forked from kamens/gae_bingo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
identity.py
184 lines (135 loc) · 6.89 KB
/
identity.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
from __future__ import absolute_import
import base64
import logging
import os
import re
from google.appengine.ext import db
from gae_bingo.config import config
from gae_bingo import cookies
from gae_bingo import request_cache
from .models import GAEBingoIdentityModel
IDENTITY_COOKIE_KEY = "gae_b_id"
IDENTITY_COOKIE_AGE = 365 * 24 * 60 * 60 # ~1 year in seconds
CAN_CONTROL_CACHE_KEY = "CAN_CONTROL_CACHE"
IDENTITY_CACHE_KEY = "IDENTITY_CACHE"
LOGGED_IN_IDENTITY_CACHE_KEY = "LOGGED_IN_IDENTITY_CACHE"
ID_TO_PUT_CACHE_KEY = "ID_TO_PUT"
def can_control_experiments():
if request_cache.cache.get(CAN_CONTROL_CACHE_KEY) is None:
request_cache.cache[CAN_CONTROL_CACHE_KEY] = (
config.can_control_experiments())
return request_cache.cache[CAN_CONTROL_CACHE_KEY]
def logged_in_bingo_identity():
if request_cache.cache.get(LOGGED_IN_IDENTITY_CACHE_KEY) is None:
request_cache.cache[LOGGED_IN_IDENTITY_CACHE_KEY] = config.current_logged_in_identity()
return request_cache.cache[LOGGED_IN_IDENTITY_CACHE_KEY]
def flush_caches():
"""Flush the caches associated with the logged in identity.
This is useful if the logged in identity changed for some reason
mid-request.
"""
request_cache.cache.pop(CAN_CONTROL_CACHE_KEY, None)
request_cache.cache.pop(IDENTITY_CACHE_KEY, None)
request_cache.cache.pop(LOGGED_IN_IDENTITY_CACHE_KEY, None)
request_cache.cache.pop(ID_TO_PUT_CACHE_KEY, None)
def identity(identity_val=None):
""" Determines the Bingo identity for the specified user. If no user
is specified, this will attempt to infer one based on cookies/logged in user
identity_val -- a string or instance of GAEBingoIdentityModel specifying
which bingo identity to retrieve.
"""
if identity_val:
# Don't cache for arbitrarily passed in identity_val
return bingo_identity_for_value(identity_val, associate_with_cookie=False)
if request_cache.cache.get(IDENTITY_CACHE_KEY) is None:
if is_bot():
# Just make all bots identify as the same single user so they don't
# bias results. Following simple suggestion in
# http://www.bingocardcreator.com/abingo/faq
request_cache.cache[IDENTITY_CACHE_KEY] = "_gae_bingo_bot"
else:
# Try to get unique (hopefully persistent) identity from user's implementation,
# otherwise grab the current cookie value, otherwise grab random value.
request_cache.cache[IDENTITY_CACHE_KEY] = str(get_logged_in_bingo_identity_value() or get_identity_cookie_value() or get_random_identity_value())
return request_cache.cache[IDENTITY_CACHE_KEY]
def using_logged_in_bingo_identity():
return identity() and identity() == get_logged_in_bingo_identity_value()
def get_logged_in_bingo_identity_value():
val = logged_in_bingo_identity()
return bingo_identity_for_value(val)
def bingo_identity_for_value(val, associate_with_cookie=True):
# We cache the ID we generate here, to put only at the end of the request
if val is None:
return None
if isinstance(val, db.Model):
if isinstance(val, GAEBingoIdentityModel):
# If it's a db.Model that inherited from GAEBingoIdentityModel, return bingo identity
if not val.gae_bingo_identity:
if (is_random_identity_value(get_identity_cookie_value()) and
associate_with_cookie):
# If the current model doesn't have a bingo identity associated w/ it
# and we have a random cookie value already set, associate it with this identity model.
#
# This keeps the user's experience consistent between using the site pre- and post-login.
request_cache.cache[ID_TO_PUT_CACHE_KEY] = get_identity_cookie_value()
else:
# Otherwise just use the key, it's guaranteed to be unique
request_cache.cache[ID_TO_PUT_CACHE_KEY] = str(val.key())
return val.gae_bingo_identity
# If it's just a normal db instance, just use its unique key
return str(val.key())
# Otherwise it's just a plain unique string
return str(val)
def get_random_identity_value():
return "_gae_bingo_random:%s" % base64.urlsafe_b64encode(os.urandom(30))
def is_random_identity_value(val):
return val and val.startswith("_gae_bingo_random")
def get_identity_cookie_value():
cookie_val = cookies.get_cookie_value(IDENTITY_COOKIE_KEY)
if cookie_val:
try:
return base64.urlsafe_b64decode(cookie_val)
except:
pass
return None
def put_id_if_necessary():
"""To be called at the end of a request.
Check to see if we should put() the gae_bingo_identity, and put() it if so.
"""
id_to_put = request_cache.cache.get(ID_TO_PUT_CACHE_KEY)
if id_to_put:
val = config.current_logged_in_identity()
if val is None:
return
if isinstance(val, GAEBingoIdentityModel):
if val.gae_bingo_identity and id_to_put != val.gae_bingo_identity:
logging.warning(
"val.gae_bingo_identity got set to %s unexpectedly,"
"but id_to_put is %s"
% (val.gae_bingo_identity, id_to_put))
else:
# If the UserData has been updated in the course of this
# request current_logged_in_identity might read a stale version
# of the UserData from the request_cache. In order to make
# sure we have the latest userData we will get the the userData
# again.
val = db.get(val.key())
val.gae_bingo_identity = id_to_put
val.put()
# Flush the transaction so the HR datastore doesn't suffer from
# eventual consistency issues when next grabbing this UserData.
db.get(val.key())
def set_identity_cookie_header():
return cookies.set_cookie_value(IDENTITY_COOKIE_KEY,
base64.urlsafe_b64encode(identity()), max_age=IDENTITY_COOKIE_AGE)
def delete_identity_cookie_header():
return cookies.set_cookie_value(IDENTITY_COOKIE_KEY, "")
# I am well aware that this is a far-from-perfect, hacky method of quickly
# determining who's a bot or not. If necessary, in the future we could implement
# a javascript check like a/bingo and django-lean do -- but for now, I'm sticking
# w/ the simplest possible implementation for devs (don't need to add JS in any template code)
# that doesn't strongly bias the statistical outcome (undetected bots aren't a distaster,
# because they shouldn't favor one side over the other).
bot_regex = re.compile("(Baidu|Gigabot|Googlebot|libwww-perl|lwp-trivial|msnbot|SiteUptime|Slurp|WordPress|ZIBB|ZyBorg)", re.IGNORECASE)
def is_bot():
return bool(bot_regex.search(os.environ.get("HTTP_USER_AGENT") or ""))