commit | author | age
|
299b4c
|
1 |
import datetime |
b95a59
|
2 |
from codecs import utf_8_decode |
CM |
3 |
from codecs import utf_8_encode |
0ee58d
|
4 |
import os |
cd1198
|
5 |
import time |
b95a59
|
6 |
|
1fa560
|
7 |
from zope.interface import implementer |
a5b033
|
8 |
|
cb5426
|
9 |
from repoze.who.interfaces import IIdentifier |
b0f81f
|
10 |
from repoze.who.interfaces import IAuthenticator |
6919cb
|
11 |
from repoze.who._compat import get_cookies |
TS |
12 |
import repoze.who._auth_tkt as auth_tkt |
9df42f
|
13 |
from repoze.who._compat import STRING_TYPES |
a5b033
|
14 |
|
798feb
|
15 |
_NOW_TESTING = None # unit tests can replace |
TS |
16 |
def _now(): #pragma NO COVERAGE |
|
17 |
if _NOW_TESTING is not None: |
|
18 |
return _NOW_TESTING |
|
19 |
return datetime.datetime.now() |
|
20 |
|
e43169
|
21 |
|
1fa560
|
22 |
@implementer(IIdentifier, IAuthenticator) |
a5b033
|
23 |
class AuthTktCookiePlugin(object): |
779caf
|
24 |
|
6919cb
|
25 |
userid_type_decoders = {'int':int} |
TS |
26 |
try: |
|
27 |
userid_type_decoders[unicode] = ('unicode', |
|
28 |
lambda x: utf_8_decode(x)[0]) |
|
29 |
except NameError: #pragma NO COVER Python >= 3.0 |
|
30 |
pass |
779caf
|
31 |
|
6919cb
|
32 |
userid_type_encoders = {int: ('int', str)} |
TS |
33 |
try: |
|
34 |
userid_type_encoders[long] = ('int', str) |
|
35 |
except NameError: #pragma NO COVER Python >= 3.0 |
|
36 |
pass |
|
37 |
try: |
|
38 |
userid_type_encoders[unicode] = ('unicode', |
|
39 |
lambda x: utf_8_encode(x)[0]) |
|
40 |
except NameError: #pragma NO COVER Python >= 3.0 |
|
41 |
pass |
|
42 |
|
a5b033
|
43 |
def __init__(self, secret, cookie_name='auth_tkt', |
cd1198
|
44 |
secure=False, include_ip=False, |
a6f6dc
|
45 |
timeout=None, reissue_time=None, userid_checker=None): |
a5b033
|
46 |
self.secret = secret |
CM |
47 |
self.cookie_name = cookie_name |
|
48 |
self.include_ip = include_ip |
|
49 |
self.secure = secure |
cd1198
|
50 |
if timeout and ( (not reissue_time) or (reissue_time > timeout) ): |
CM |
51 |
raise ValueError('When timeout is specified, reissue_time must ' |
|
52 |
'be set to a lower value') |
|
53 |
self.timeout = timeout |
|
54 |
self.reissue_time = reissue_time |
a6f6dc
|
55 |
self.userid_checker = userid_checker |
a5b033
|
56 |
|
CM |
57 |
# IIdentifier |
|
58 |
def identify(self, environ): |
|
59 |
cookies = get_cookies(environ) |
|
60 |
cookie = cookies.get(self.cookie_name) |
|
61 |
|
|
62 |
if cookie is None or not cookie.value: |
40a968
|
63 |
return None |
a5b033
|
64 |
|
CM |
65 |
if self.include_ip: |
|
66 |
remote_addr = environ['REMOTE_ADDR'] |
|
67 |
else: |
|
68 |
remote_addr = '0.0.0.0' |
|
69 |
|
|
70 |
try: |
|
71 |
timestamp, userid, tokens, user_data = auth_tkt.parse_ticket( |
|
72 |
self.secret, cookie.value, remote_addr) |
|
73 |
except auth_tkt.BadTicket: |
a6f6dc
|
74 |
return None |
CM |
75 |
|
cd1198
|
76 |
if self.timeout and ( (timestamp + self.timeout) < time.time() ): |
40a968
|
77 |
return None |
779caf
|
78 |
|
CM |
79 |
userid_typename = 'userid_type:' |
|
80 |
user_data_info = user_data.split('|') |
|
81 |
for datum in filter(None, user_data_info): |
|
82 |
if datum.startswith(userid_typename): |
|
83 |
userid_type = datum[len(userid_typename):] |
|
84 |
decoder = self.userid_type_decoders.get(userid_type) |
|
85 |
if decoder: |
|
86 |
userid = decoder(userid) |
a5b033
|
87 |
|
CM |
88 |
environ['REMOTE_USER_TOKENS'] = tokens |
|
89 |
environ['REMOTE_USER_DATA'] = user_data |
|
90 |
environ['AUTH_TYPE'] = 'cookie' |
56d0c5
|
91 |
|
a5b033
|
92 |
identity = {} |
CM |
93 |
identity['timestamp'] = timestamp |
b0f81f
|
94 |
identity['repoze.who.plugins.auth_tkt.userid'] = userid |
a5b033
|
95 |
identity['tokens'] = tokens |
CM |
96 |
identity['userdata'] = user_data |
|
97 |
return identity |
519300
|
98 |
|
CM |
99 |
# IIdentifier |
|
100 |
def forget(self, environ, identity): |
|
101 |
# return a set of expires Set-Cookie headers |
798feb
|
102 |
return self._get_cookies(environ, 'INVALID', 0) |
a5b033
|
103 |
|
CM |
104 |
# IIdentifier |
|
105 |
def remember(self, environ, identity): |
|
106 |
if self.include_ip: |
|
107 |
remote_addr = environ['REMOTE_ADDR'] |
|
108 |
else: |
|
109 |
remote_addr = '0.0.0.0' |
|
110 |
|
|
111 |
cookies = get_cookies(environ) |
|
112 |
old_cookie = cookies.get(self.cookie_name) |
|
113 |
existing = cookies.get(self.cookie_name) |
|
114 |
old_cookie_value = getattr(existing, 'value', None) |
299b4c
|
115 |
max_age = identity.get('max_age', None) |
a5b033
|
116 |
|
fc9a88
|
117 |
timestamp, userid, tokens, userdata = None, '', (), '' |
a5b033
|
118 |
|
CM |
119 |
if old_cookie_value: |
|
120 |
try: |
|
121 |
timestamp,userid,tokens,userdata = auth_tkt.parse_ticket( |
|
122 |
self.secret, old_cookie_value, remote_addr) |
|
123 |
except auth_tkt.BadTicket: |
|
124 |
pass |
fc9a88
|
125 |
tokens = tuple(tokens) |
a5b033
|
126 |
|
cb5426
|
127 |
who_userid = identity['repoze.who.userid'] |
fc9a88
|
128 |
who_tokens = tuple(identity.get('tokens', ())) |
cb5426
|
129 |
who_userdata = identity.get('userdata', '') |
779caf
|
130 |
|
CM |
131 |
encoding_data = self.userid_type_encoders.get(type(who_userid)) |
|
132 |
if encoding_data: |
|
133 |
encoding, encoder = encoding_data |
|
134 |
who_userid = encoder(who_userid) |
12946e
|
135 |
# XXX we are discarding the userdata passed in the identity? |
779caf
|
136 |
who_userdata = 'userid_type:%s' % encoding |
a5b033
|
137 |
|
CM |
138 |
old_data = (userid, tokens, userdata) |
cb5426
|
139 |
new_data = (who_userid, who_tokens, who_userdata) |
a5b033
|
140 |
|
cd1198
|
141 |
if old_data != new_data or (self.reissue_time and |
CM |
142 |
( (timestamp + self.reissue_time) < time.time() )): |
a5b033
|
143 |
ticket = auth_tkt.AuthTicket( |
CM |
144 |
self.secret, |
cb5426
|
145 |
who_userid, |
a5b033
|
146 |
remote_addr, |
cb5426
|
147 |
tokens=who_tokens, |
CM |
148 |
user_data=who_userdata, |
a5b033
|
149 |
cookie_name=self.cookie_name, |
CM |
150 |
secure=self.secure) |
|
151 |
new_cookie_value = ticket.cookie_value() |
519300
|
152 |
|
a5b033
|
153 |
if old_cookie_value != new_cookie_value: |
519300
|
154 |
# return a set of Set-Cookie headers |
299b4c
|
155 |
return self._get_cookies(environ, new_cookie_value, max_age) |
a5b033
|
156 |
|
b0f81f
|
157 |
# IAuthenticator |
TS |
158 |
def authenticate(self, environ, identity): |
|
159 |
userid = identity.get('repoze.who.plugins.auth_tkt.userid') |
|
160 |
if userid is None: |
|
161 |
return None |
|
162 |
if self.userid_checker and not self.userid_checker(userid): |
|
163 |
return None |
|
164 |
identity['repoze.who.userid'] = userid |
|
165 |
return userid |
|
166 |
|
|
167 |
def _get_cookies(self, environ, value, max_age=None): |
|
168 |
if max_age is not None: |
15e365
|
169 |
max_age = int(max_age) |
TS |
170 |
later = _now() + datetime.timedelta(seconds=max_age) |
b0f81f
|
171 |
# Wdy, DD-Mon-YY HH:MM:SS GMT |
TS |
172 |
expires = later.strftime('%a, %d %b %Y %H:%M:%S') |
|
173 |
# the Expires header is *required* at least for IE7 (IE7 does |
|
174 |
# not respect Max-Age) |
|
175 |
max_age = "; Max-Age=%s; Expires=%s" % (max_age, expires) |
|
176 |
else: |
|
177 |
max_age = '' |
|
178 |
|
d7e647
|
179 |
secure = '' |
BS |
180 |
if self.secure: |
3b5782
|
181 |
secure = '; secure; HttpOnly' |
d7e647
|
182 |
|
b0f81f
|
183 |
cur_domain = environ.get('HTTP_HOST', environ.get('SERVER_NAME')) |
7f9907
|
184 |
cur_domain = cur_domain.split(':')[0] # drop port |
b0f81f
|
185 |
wild_domain = '.' + cur_domain |
TS |
186 |
cookies = [ |
d7e647
|
187 |
('Set-Cookie', '%s="%s"; Path=/%s%s' % ( |
BS |
188 |
self.cookie_name, value, max_age, secure)), |
|
189 |
('Set-Cookie', '%s="%s"; Path=/; Domain=%s%s%s' % ( |
|
190 |
self.cookie_name, value, cur_domain, max_age, secure)), |
|
191 |
('Set-Cookie', '%s="%s"; Path=/; Domain=%s%s%s' % ( |
|
192 |
self.cookie_name, value, wild_domain, max_age, secure)) |
b0f81f
|
193 |
] |
TS |
194 |
return cookies |
|
195 |
|
a5b033
|
196 |
def __repr__(self): |
394ea6
|
197 |
return '<%s %s>' % (self.__class__.__name__, |
TS |
198 |
id(self)) #pragma NO COVERAGE |
a5b033
|
199 |
|
515c69
|
200 |
def _bool(value): |
9df42f
|
201 |
if isinstance(value, STRING_TYPES): |
515c69
|
202 |
return value.lower() in ('yes', 'true', '1') |
TS |
203 |
return value |
|
204 |
|
|
205 |
def make_plugin(secret=None, |
0ee58d
|
206 |
secretfile=None, |
a5b033
|
207 |
cookie_name='auth_tkt', |
0ee58d
|
208 |
secure=False, |
TS |
209 |
include_ip=False, |
cd1198
|
210 |
timeout=None, |
CM |
211 |
reissue_time=None, |
a6f6dc
|
212 |
userid_checker=None, |
0ee58d
|
213 |
): |
a6f6dc
|
214 |
from repoze.who.utils import resolveDotted |
0ee58d
|
215 |
if (secret is None and secretfile is None): |
TS |
216 |
raise ValueError("One of 'secret' or 'secretfile' must not be None.") |
|
217 |
if (secret is not None and secretfile is not None): |
|
218 |
raise ValueError("Specify only one of 'secret' or 'secretfile'.") |
|
219 |
if secretfile: |
|
220 |
secretfile = os.path.abspath(os.path.expanduser(secretfile)) |
|
221 |
if not os.path.exists(secretfile): |
|
222 |
raise ValueError("No such 'secretfile': %s" % secretfile) |
|
223 |
secret = open(secretfile).read().strip() |
cd1198
|
224 |
if timeout: |
CM |
225 |
timeout = int(timeout) |
|
226 |
if reissue_time: |
|
227 |
reissue_time = int(reissue_time) |
a6f6dc
|
228 |
if userid_checker is not None: |
CM |
229 |
userid_checker = resolveDotted(userid_checker) |
cd1198
|
230 |
plugin = AuthTktCookiePlugin(secret, |
CM |
231 |
cookie_name, |
|
232 |
_bool(secure), |
|
233 |
_bool(include_ip), |
|
234 |
timeout, |
|
235 |
reissue_time, |
a6f6dc
|
236 |
userid_checker, |
cd1198
|
237 |
) |
a5b033
|
238 |
return plugin |
CM |
239 |
|