Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 1009 lines (821 sloc) 34.189 kb
df971c3 fix the licenses so they stop polluting all the other diffs
spez authored
1 # The contents of this file are subject to the Common Public Attribution
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
2 # License Version 1.0. (the "License"); you may not use this file except in
3 # compliance with the License. You may obtain a copy of the License at
4 # http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
5 # License Version 1.1, but Sections 14 and 15 have been added to cover use of
6 # software over a computer network and provide for limited attribution for the
7 # Original Developer. In addition, Exhibit A has been modified to be consistent
8 # with Exhibit B.
2869eaf david king New features:
ketralnis authored
9 #
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
10 # Software distributed under the License is distributed on an "AS IS" basis,
11 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
12 # the specific language governing rights and limitations under the License.
2869eaf david king New features:
ketralnis authored
13 #
914b949 Neil Williams Update / add license headers.
spladug authored
14 # The Original Code is reddit.
2869eaf david king New features:
ketralnis authored
15 #
914b949 Neil Williams Update / add license headers.
spladug authored
16 # The Original Developer is the Initial Developer. The Initial Developer of
17 # the Original Code is reddit Inc.
2869eaf david king New features:
ketralnis authored
18 #
af09fa8 Neil Williams Update license headers to 2015.
spladug authored
19 # All portions of the code written by reddit are Copyright (c) 2006-2015 reddit
914b949 Neil Williams Update / add license headers.
spladug authored
20 # Inc. All Rights Reserved.
21 ###############################################################################
22
25a890d Jordan Milne Replace `disable_require_employee_https` with a feature flag
JordanMilne authored
23 from r2.config import feature
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
24 from r2.lib.db.thing import Thing, Relation, NotFound
25 from r2.lib.db.operators import lower
26 from r2.lib.db.userrel import UserRel
2fe83ed Jason Harvey Add AccountActivityBySR.
alienth authored
27 from r2.lib.db import tdb_cassandra
e35b519 * removed references to clear_memo
spez authored
28 from r2.lib.memoize import memoize
8a5f685 Max Goodman Move private-shadowed utils into separate module.
chromakode authored
29 from r2.lib.admin_utils import modhash, valid_hash
30 from r2.lib.utils import randstr, timefromnow
2243af7 Neil Williams Clean out dead last-modified rel code.
spladug authored
31 from r2.lib.utils import UrlParser
76594cd Neil Williams Refactor and reorganize email canonicalization; add some tests.
spladug authored
32 from r2.lib.utils import constant_time_compare, canonicalize_email
2869eaf david king New features:
ketralnis authored
33 from r2.lib.cache import sgm
3437713 Roger Ostrander Email: Use non-hardcache ban fetching system
atiaxi authored
34 from r2.lib import filters, hooks
37e2ba9 david king * Combine cassandra clusters into a single one
ketralnis authored
35 from r2.lib.log import log_text
555c02a Neil Williams Dual-write last visit timestamp.
spladug authored
36 from r2.models.last_modified import LastModified
d2ccc40 Neil Williams Automatically delete password hashes of deleted accounts.
spladug authored
37 from r2.models.trylater import TryLater
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
38
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
39 from pylons import c, g, request
88d191e Neil Williams Gold feature: personal karma breakdown by subreddit.
spladug authored
40 from pylons.i18n import _
c9c65dd Neil Williams Replace references to deprecated sha module with hashlib.
spladug authored
41 import time
42 import hashlib
b8d9981 Chad Birch Refactor ProfileBar and Account.all_karmas
Deimos authored
43 from collections import Counter, OrderedDict
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
44 from copy import copy
bf9f43c Christopher Slowe Messaging/commenting
KeyserSosa authored
45 from datetime import datetime, timedelta
a311805 Neil Williams Switch to bcrypt for password hashing.
spladug authored
46 import bcrypt
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
47 import hmac
48 import hashlib
3437713 Roger Ostrander Email: Use non-hardcache ban fetching system
atiaxi authored
49 import itertools
2fe83ed Jason Harvey Add AccountActivityBySR.
alienth authored
50 from pycassa.system_manager import ASCII_TYPE
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
51
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
52
d2ccc40 Neil Williams Automatically delete password hashes of deleted accounts.
spladug authored
53 trylater_hooks = hooks.HookRegistrar()
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
54 COOKIE_TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S'
55
56
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
57 class AccountExists(Exception): pass
58
59 class Account(Thing):
60 _data_int_props = Thing._data_int_props + ('link_karma', 'comment_karma',
61 'report_made', 'report_correct',
62 'report_ignored', 'spammer',
09c98d6 Chris Dary Inbox counts: Add dark unread counts badge, start writing to inbox_count
umbrae authored
63 'reported', 'gold_creddits',
64 'inbox_count',
b76322f Brian Simpson Add alerts for suspicious selfserve advertising payments.
bsimpson63 authored
65 'num_payment_methods',
66 'num_failed_payments',
09c98d6 Chris Dary Inbox counts: Add dark unread counts badge, start writing to inbox_count
umbrae authored
67 )
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
68 _int_prop_suffix = '_karma'
9a4271f Christopher Slowe Upgrade Instructions
KeyserSosa authored
69 _essentials = ('name', )
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
70 _defaults = dict(pref_numsites = 25,
71 pref_frame = False,
e7eb9e6 comments panel off by default
spez authored
72 pref_frame_commentspanel = False,
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
73 pref_newwindow = False,
ae9eaf5 add preference to remove recently clicked widget
spez authored
74 pref_clickgadget = 5,
ff002a4 david king Add backend for new gold feature: "remember my visits".
ketralnis authored
75 pref_store_visits = False,
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
76 pref_public_votes = False,
1b030c3 Chris Johnson Added option to hide user profile from robots.
raugturi authored
77 pref_hide_from_robots = False,
37e2ba9 david king * Combine cassandra clusters into a single one
ketralnis authored
78 pref_research = False,
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
79 pref_hide_ups = False,
4440ccf Christopher Slowe overhaul of JS and form handling code, not based on jQuery
KeyserSosa authored
80 pref_hide_downs = False,
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
81 pref_min_link_score = -4,
82 pref_min_comment_score = -4,
83 pref_num_comments = g.num_comments,
c47c697 Chris Dary Add optional controversial indicator to comment scores
umbrae authored
84 pref_highlight_controversial=False,
0745f5b Christopher Slowe make 1/2 of the default reddits English reddits if the user hasn't set t...
KeyserSosa authored
85 pref_lang = g.lang,
86 pref_content_langs = (g.lang,),
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
87 pref_over_18 = False,
88 pref_compress = False,
7e6cbc4 Chad Birch Domain area: add new user pref to show extra info
Deimos authored
89 pref_domain_details = False,
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
90 pref_organic = True,
08c431b Christopher Slowe * Comply with the spec on 304 errors so Chrome won't barf download.gz a...
KeyserSosa authored
91 pref_no_profanity = True,
5ef76b9 Christopher Slowe New features:
KeyserSosa authored
92 pref_label_nsfw = True,
6bcef00 david king 1. Allow a reddit to have a cname, like www.proggit.com, that renders
ketralnis authored
93 pref_show_stylesheets = True,
4194ced Add a user preference for whether to show flair.
Logan Hanks authored
94 pref_show_flair = True,
13e191b Add a user preference for showing link flair.
Logan Hanks authored
95 pref_show_link_flair = True,
5ef76b9 Christopher Slowe New features:
KeyserSosa authored
96 pref_mark_messages_read = True,
2869eaf david king New features:
ketralnis authored
97 pref_threaded_messages = True,
98 pref_collapse_read_messages = False,
a402d48 New features:
Mike authored
99 pref_private_feeds = True,
b70556a Jordan Milne Add support for forced HTTPS with HSTS grants
JordanMilne authored
100 pref_force_https = False,
0ae8f2f david king 21 Jul 2010 merge
ketralnis authored
101 pref_show_adbox = True,
7fff900 david king February 2011 Merge
ketralnis authored
102 pref_show_sponsors = True, # sponsored links
103 pref_show_sponsorships = True,
a453758 Chris Dary Add a preference for trending on the front page
umbrae authored
104 pref_show_trending=True,
37e2ba9 david king * Combine cassandra clusters into a single one
ketralnis authored
105 pref_highlight_new_comments = True,
e057278 Neil Williams Gold Feature: "The Butler". Username monitoring in comments.
spladug authored
106 pref_monitor_mentions=True,
07366ac Max Goodman Move left bar collapsing to server-side preference.
chromakode authored
107 pref_collapse_left_bar=False,
8c9b31d Brian Simpson ServerSecondsBar can be made public.
bsimpson63 authored
108 pref_public_server_seconds=False,
9a4271f Christopher Slowe Upgrade Instructions
KeyserSosa authored
109 mobile_compress = False,
110 mobile_thumbnail = True,
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
111 reported = 0,
112 report_made = 0,
113 report_correct = 0,
114 report_ignored = 0,
115 spammer = 0,
116 sort_options = {},
950971c added last modified to profile/comment pages
shuffman authored
117 has_subscribed = False,
0c04094 Christopher Slowe updates to RSS feed to link to permalink page instead of goto. Also add...
KeyserSosa authored
118 pref_media = 'subreddit',
603ec0e Ricky Ramirez wiki: Fix bug with wiki permissions.
rram authored
119 wiki_override = None,
bf9f43c Christopher Slowe Messaging/commenting
KeyserSosa authored
120 email = "",
e87f520 Christopher Slowe New Features:
KeyserSosa authored
121 email_verified = False,
bf9f43c Christopher Slowe Messaging/commenting
KeyserSosa authored
122 ignorereports = False,
0ae8f2f david king 21 Jul 2010 merge
ketralnis authored
123 pref_show_promote = None,
124 gold = False,
37e2ba9 david king * Combine cassandra clusters into a single one
ketralnis authored
125 gold_charter = False,
7fff900 david king February 2011 Merge
ketralnis authored
126 gold_creddits = 0,
acd485e Neil Williams Add default for new cake system.
spladug authored
127 cake_expiration=None,
8dfd73b Neil Williams Add framework for RFC-6238: Time-Based One Time Password Algorithm.
spladug authored
128 otp_secret=None,
65b1467 Roger Ostrander Backend support for bans.
atiaxi authored
129 state=0,
5e249f4 Neil Williams Make all moderators have a modmsgtime attribute.
spladug authored
130 modmsgtime=None,
09c98d6 Chris Dary Inbox counts: Add dark unread counts badge, start writing to inbox_count
umbrae authored
131 inbox_count=0,
594b6bf Roger Ostrander Listingcontroller: Optionally display banned userpages
atiaxi authored
132 banned_profile_visible=False,
ac83646 Brian Simpson Add a notice when localized default subreddits are used.
bsimpson63 authored
133 pref_use_global_defaults=False,
134 pref_hide_locationbar=False,
82b870d Chad Birch Gold: add preference to auto-renew with a creddit
Deimos authored
135 pref_creddit_autorenew=False,
f963cb7 Chad Birch Support certain accounts never adding to sent
Deimos authored
136 update_sent_messages=True,
0055f9a Melissa Cole Can reveal identity or write a message in gildings
MelissaCole authored
137 num_payment_methods=0,
138 num_failed_payments=0,
139 pref_show_snoovatar=False,
140 gild_reveal_username=False,
dbb7935 Brian Simpson Enable per-user global selfserve cpm override.
bsimpson63 authored
141 selfserve_cpm_override_pennies=None,
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
142 )
c413a2e Keith Mitchell Add PATCH /api/v1/me/prefs endpoint
kemitche authored
143 _preference_attrs = tuple(k for k in _defaults.keys()
144 if k.startswith("pref_"))
145
146 def preferences(self):
147 return {pref: getattr(self, pref) for pref in self._preference_attrs}
950971c added last modified to profile/comment pages
shuffman authored
148
b06dc9b Max Goodman Add equality implementation for Account model.
chromakode authored
149 def __eq__(self, other):
150 if type(self) != type(other):
151 return False
152
153 return self._id == other._id
154
155 def __ne__(self, other):
156 return not self.__eq__(other)
157
e8e751a Neil Williams Don't spam ban messages for users who don't care.
spladug authored
158 def has_interacted_with(self, sr):
159 if not sr:
160 return False
161
162 for type in ('link', 'comment'):
163 if hasattr(self, "%s_%s_karma" % (sr.name, type)):
164 return True
165
166 if sr.is_subscriber(self):
167 return True
168
169 return False
170
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
171 def karma(self, kind, sr = None):
172 suffix = '_' + kind + '_karma'
e87f520 Christopher Slowe New Features:
KeyserSosa authored
173
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
174 #if no sr, return the sum
175 if sr is None:
176 total = 0
177 for k, v in self._t.iteritems():
178 if k.endswith(suffix):
179 total += v
180 return total
181 else:
182 try:
183 return getattr(self, sr.name + suffix)
184 except AttributeError:
185 #if positive karma elsewhere, you get min_up_karma
186 if self.karma(kind) > 0:
187 return g.MIN_UP_KARMA
188 else:
189 return 0
190
191 def incr_karma(self, kind, sr, amt):
37e2ba9 david king * Combine cassandra clusters into a single one
ketralnis authored
192 if sr.name.startswith('_'):
193 g.log.info("Ignoring karma increase for subreddit %r" % (sr.name,))
194 return
195
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
196 prop = '%s_%s_karma' % (sr.name, kind)
197 if hasattr(self, prop):
198 return self._incr(prop, amt)
199 else:
200 default_val = self.karma(kind, sr)
201 setattr(self, prop, default_val + amt)
202 self._commit()
203
204 @property
205 def link_karma(self):
206 return self.karma('link')
207
208 @property
209 def comment_karma(self):
210 return self.karma('comment')
211
a635c9c Andre d karma: Add karma list api endpoint.
andre-d authored
212 def all_karmas(self, include_old=True):
b8d9981 Chad Birch Refactor ProfileBar and Account.all_karmas
Deimos authored
213 """Get all of the user's subreddit-specific karma totals.
214
215 Returns an OrderedDict keyed on subreddit name and containing
216 (link_karma, comment_karma) tuples, ordered by the combined total
217 descending.
218 """
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
219 link_suffix = '_link_karma'
220 comment_suffix = '_comment_karma'
b8d9981 Chad Birch Refactor ProfileBar and Account.all_karmas
Deimos authored
221
222 comment_karmas = Counter()
223 link_karmas = Counter()
224 combined_karmas = Counter()
225
226 for key, value in self._t.iteritems():
227 if key.endswith(link_suffix):
228 sr_name = key[:-len(link_suffix)]
229 link_karmas[sr_name] = value
230 elif key.endswith(comment_suffix):
231 sr_name = key[:-len(comment_suffix)]
232 comment_karmas[sr_name] = value
233 else:
234 continue
235
236 combined_karmas[sr_name] += value
237
238 all_karmas = OrderedDict()
239 for sr_name, total in combined_karmas.most_common():
240 all_karmas[sr_name] = (link_karmas[sr_name],
241 comment_karmas[sr_name])
242
243 if include_old:
244 old_link_karma = self._t.get('link_karma', 0)
245 old_comment_karma = self._t.get('comment_karma', 0)
246 if old_link_karma or old_comment_karma:
247 all_karmas['ancient history'] = (old_link_karma,
248 old_comment_karma)
249
250 return all_karmas
bf9f43c Christopher Slowe Messaging/commenting
KeyserSosa authored
251
252 def update_last_visit(self, current_time):
253 from admintools import apply_updates
254
255 apply_updates(self)
256
14cb34c Neil Williams LastModified: cut reads for Last Visit over to new schema.
spladug authored
257 prev_visit = LastModified.get(self._fullname, "Visit")
258 if prev_visit and current_time - prev_visit < timedelta(days=1):
bf9f43c Christopher Slowe Messaging/commenting
KeyserSosa authored
259 return
260
9a4271f Christopher Slowe Upgrade Instructions
KeyserSosa authored
261 g.log.debug ("Updating last visit for %s from %s to %s" %
262 (self.name, prev_visit, current_time))
bf9f43c Christopher Slowe Messaging/commenting
KeyserSosa authored
263
555c02a Neil Williams Dual-write last visit timestamp.
spladug authored
264 LastModified.touch(self._fullname, "Visit")
265
b71d8bf Jason Harvey Store a last_visit time on the Account thing.
alienth authored
266 self.last_visit = int(time.time())
267 self._commit()
268
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
269 def make_cookie(self, timestr=None):
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
270 if not self._loaded:
271 self._load()
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
272 timestr = timestr or time.strftime(COOKIE_TIMESTAMP_FORMAT)
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
273 id_time = str(self._id) + ',' + timestr
3366083 Neil Williams Create a vault for secret tokens and move some into it.
spladug authored
274 to_hash = ','.join((id_time, self.password, g.secrets["SECRET"]))
c9c65dd Neil Williams Replace references to deprecated sha module with hashlib.
spladug authored
275 return id_time + ',' + hashlib.sha1(to_hash).hexdigest()
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
276
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
277 def make_admin_cookie(self, first_login=None, last_request=None):
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
278 if not self._loaded:
279 self._load()
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
280 first_login = first_login or datetime.utcnow().strftime(COOKIE_TIMESTAMP_FORMAT)
281 last_request = last_request or datetime.utcnow().strftime(COOKIE_TIMESTAMP_FORMAT)
282 hashable = ','.join((first_login, last_request, request.ip, request.user_agent, self.password))
3366083 Neil Williams Create a vault for secret tokens and move some into it.
spladug authored
283 mac = hmac.new(g.secrets["SECRET"], hashable, hashlib.sha1).hexdigest()
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
284 return ','.join((first_login, last_request, mac))
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
285
8dfd73b Neil Williams Add framework for RFC-6238: Time-Based One Time Password Algorithm.
spladug authored
286 def make_otp_cookie(self, timestamp=None):
287 if not self._loaded:
288 self._load()
289
290 timestamp = timestamp or datetime.utcnow().strftime(COOKIE_TIMESTAMP_FORMAT)
291 secrets = [request.user_agent, self.otp_secret, self.password]
3366083 Neil Williams Create a vault for secret tokens and move some into it.
spladug authored
292 signature = hmac.new(g.secrets["SECRET"], ','.join([timestamp] + secrets), hashlib.sha1).hexdigest()
8dfd73b Neil Williams Add framework for RFC-6238: Time-Based One Time Password Algorithm.
spladug authored
293
294 return ",".join((timestamp, signature))
295
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
296 def needs_captcha(self):
9c8be78 Chad Birch Account.needs_captcha: add hook, use live_config
Deimos authored
297 if g.disable_captcha:
298 return False
299
300 hook = hooks.get_hook("account.is_captcha_exempt")
301 captcha_exempt = hook.call_until_return(account=self)
302 if captcha_exempt:
303 return False
304
305 if self.link_karma >= g.live_config["captcha_exempt_link_karma"]:
306 return False
307
308 if self.comment_karma >= g.live_config["captcha_exempt_comment_karma"]:
309 return False
310
311 return True
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
312
165ca78 Chad Birch Create subreddit: add support for min age/karma
Deimos authored
313 @property
314 def can_create_subreddit(self):
315 hook = hooks.get_hook("account.can_create_subreddit")
316 can_create = hook.call_until_return(account=self)
317 if can_create is not None:
318 return can_create
319
320 min_age = timedelta(days=g.live_config["create_sr_account_age_days"])
321 if self._age < min_age:
322 return False
323
324 if (self.link_karma < g.live_config["create_sr_link_karma"] and
325 self.comment_karma < g.live_config["create_sr_comment_karma"]):
326 return False
327
328 return True
329
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
330 def modhash(self, rand=None, test=False):
d6848c8 Keith Mitchell [OAuth2] Don't send unnecessary modhash to OAuth clients
kemitche authored
331 if c.oauth_user:
332 # OAuth clients should never receive a modhash of any kind
333 # as they could use it in a CSRF attack to bypass their
334 # permitted OAuth scopes.
335 return None
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
336 return modhash(self, rand = rand, test = test)
337
338 def valid_hash(self, hash):
deff940 Max Goodman Add OAuth2 handling to the main APIController.
chromakode authored
339 if self == c.oauth_user:
340 # OAuth authenticated requests do not require CSRF protection.
341 return True
342 else:
343 return valid_hash(self, hash)
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
344
345 @classmethod
346 @memoize('account._by_name')
347 def _by_name_cache(cls, name, allow_deleted = False):
348 #relower name here, just in case
349 deleted = (True, False) if allow_deleted else False
350 q = cls._query(lower(Account.c.name) == name.lower(),
351 Account.c._spam == (True, False),
352 Account.c._deleted == deleted)
353
354 q._limit = 1
355 l = list(q)
356 if l:
357 return l[0]._id
358
359 @classmethod
e35b519 * removed references to clear_memo
spez authored
360 def _by_name(cls, name, allow_deleted = False, _update = False):
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
361 #lower name here so there is only one cache
e35b519 * removed references to clear_memo
spez authored
362 uid = cls._by_name_cache(name.lower(), allow_deleted, _update = _update)
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
363 if uid:
364 return cls._byID(uid, True)
365 else:
366 raise NotFound, 'Account %s' % name
367
ee5ea8c Chris Dary Inbox_counts: corrections on deletes, spams, edits
umbrae authored
368 @classmethod
369 def _names_to_ids(cls, names, ignore_missing=False, allow_deleted=False,
370 _update=False):
371 for name in names:
372 uid = cls._by_name_cache(name.lower(), allow_deleted, _update=_update)
373 if not uid:
374 if ignore_missing:
375 continue
376 raise NotFound('Account %s' % name)
377 yield uid
378
bf9f43c Christopher Slowe Messaging/commenting
KeyserSosa authored
379 # Admins only, since it's not memoized
380 @classmethod
381 def _by_name_multiple(cls, name):
382 q = cls._query(lower(Account.c.name) == name.lower(),
383 Account.c._spam == (True, False),
384 Account.c._deleted == (True, False))
385 return list(q)
386
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
387 @property
388 def friends(self):
389 return self.friend_ids()
390
4d7a2fa Keith Mitchell Allow users to block users that harass them
kemitche authored
391 @property
392 def enemies(self):
393 return self.enemy_ids()
394
5e249f4 Neil Williams Make all moderators have a modmsgtime attribute.
spladug authored
395 @property
396 def is_moderator_somewhere(self):
397 # modmsgtime can be:
398 # - a date: the user is a mod somewhere and has unread modmail
399 # - False: the user is a mod somewhere and has no unread modmail
400 # - None: (the default) the user is not a mod anywhere
401 return self.modmsgtime is not None
402
0ae8f2f david king 21 Jul 2010 merge
ketralnis authored
403 # Used on the goldmember version of /prefs/friends
404 @memoize('account.friend_rels')
405 def friend_rels_cache(self):
406 q = Friend._query(Friend.c._thing1_id == self._id,
407 Friend.c._name == 'friend')
408 return list(f._id for f in q)
409
410 def friend_rels(self, _update = False):
411 rel_ids = self.friend_rels_cache(_update=_update)
37e2ba9 david king * Combine cassandra clusters into a single one
ketralnis authored
412 try:
413 rels = Friend._byID_rel(rel_ids, return_dict=False,
414 eager_load = True, data = True,
415 thing_data = True)
416 rels = list(rels)
417 except NotFound:
418 if _update:
419 raise
420 else:
421 log_text("friend-rels-bandaid 1",
422 "Had to recalc friend_rels (1) for %s" % self.name,
423 "warning")
424 return self.friend_rels(_update=True)
425
426 if not _update:
427 sorted_1 = sorted([r._thing2_id for r in rels])
428 sorted_2 = sorted(list(self.friends))
429 if sorted_1 != sorted_2:
430 g.log.error("FR1: %r" % sorted_1)
431 g.log.error("FR2: %r" % sorted_2)
432 log_text("friend-rels-bandaid 2",
433 "Had to recalc friend_rels (2) for %s" % self.name,
434 "warning")
435 self.friend_ids(_update=True)
436 return self.friend_rels(_update=True)
0ae8f2f david king 21 Jul 2010 merge
ketralnis authored
437 return dict((r._thing2_id, r) for r in rels)
438
439 def add_friend_note(self, friend, note):
440 rels = self.friend_rels()
441 rel = rels[friend._id]
442 rel.note = note
443 rel._commit()
444
ea1fb6f Max Goodman Redesign the account deletion page.
chromakode authored
445 def delete(self, delete_message=None):
446 self.delete_message = delete_message
f1db8ef Roger Ostrander API: Record account deletion time
atiaxi authored
447 self.delete_time = datetime.now(g.tz)
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
448 self._deleted = True
449 self._commit()
e35b519 * removed references to clear_memo
spez authored
450
451 #update caches
452 Account._by_name(self.name, allow_deleted = True, _update = True)
453 #we need to catch an exception here since it will have been
454 #recently deleted
455 try:
456 Account._by_name(self.name, _update = True)
457 except NotFound:
458 pass
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
459
460 #remove from friends lists
461 q = Friend._query(Friend.c._thing2_id == self._id,
462 Friend.c._name == 'friend',
463 eager_load = True)
464 for f in q:
465 f._thing1.remove_friend(f._thing2)
466
4d7a2fa Keith Mitchell Allow users to block users that harass them
kemitche authored
467 q = Friend._query(Friend.c._thing2_id == self._id,
468 Friend.c._name == 'enemy',
469 eager_load=True)
470 for f in q:
471 f._thing1.remove_enemy(f._thing2)
472
d2ccc40 Neil Williams Automatically delete password hashes of deleted accounts.
spladug authored
473 # wipe out stored password data after a recovery period
474 TryLater.schedule("account_deletion", self._id36,
475 delay=timedelta(days=90))
476
5765572 Dave Pifke Tweaks to OAuth2 models.
dpifke authored
477 # Remove OAuth2Client developer permissions. This will delete any
478 # clients for which this account is the sole developer.
d4d2214 Fix ImportError in Account.delete (oauth2 -> token).
Logan Hanks authored
479 from r2.models.token import OAuth2Client
5765572 Dave Pifke Tweaks to OAuth2 models.
dpifke authored
480 for client in OAuth2Client._by_developer(self):
481 client.remove_developer(self)
482
65b1467 Roger Ostrander Backend support for bans.
atiaxi authored
483 # 'State' bitfield properties
484 @property
485 def _banned(self):
486 return self.state & 1
487
488 @_banned.setter
489 def _banned(self, value):
490 if value and not self._banned:
491 self.state |= 1
492 # Invalidate all cookies by changing the password
493 # First back up the password so we can reverse this
494 self.backup_password = self.password
495 # New PW doesn't matter, they can't log in with it anyway.
496 # Even if their PW /was/ 'banned' for some reason, this
497 # will change the salt and thus invalidate the cookies
498 change_password(self, 'banned')
499
500 # deauthorize all access tokens
501 from r2.models.token import OAuth2AccessToken
502 from r2.models.token import OAuth2RefreshToken
503
504 OAuth2AccessToken.revoke_all_by_user(self)
505 OAuth2RefreshToken.revoke_all_by_user(self)
506 elif not value and self._banned:
507 self.state &= ~1
508
509 # Undo the password thing so they can log in
510 self.password = self.backup_password
511
512 # They're on their own for OAuth tokens, though.
513
514 self._commit()
515
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
516 @property
517 def subreddits(self):
518 from subreddit import Subreddit
519 return Subreddit.user_subreddits(self)
520
3735033 Max Goodman Add support for special user distingushes.
chromakode authored
521 def special_distinguish(self):
522 if self._t.get("special_distinguish_name"):
523 return dict((k, self._t.get("special_distinguish_"+k, None))
524 for k in ("name", "kind", "symbol", "cssclass", "label", "link"))
525 else:
526 return None
527
e87f520 Christopher Slowe New Features:
KeyserSosa authored
528 def quota_key(self, kind):
529 return "user_%s_quotas-%s" % (kind, self.name)
530
531 def clog_quota(self, kind, item):
532 key = self.quota_key(kind)
533 fnames = g.hardcache.get(key, [])
534 fnames.append(item._fullname)
535 g.hardcache.set(key, fnames, 86400 * 30)
536
537 def quota_baskets(self, kind):
538 from r2.models.admintools import filter_quotas
539 key = self.quota_key(kind)
540 fnames = g.hardcache.get(key)
541
542 if not fnames:
543 return None
544
545 unfiltered = Thing._by_fullname(fnames, data=True, return_dict=False)
546
547 baskets, new_quotas = filter_quotas(unfiltered)
548
549 if new_quotas is None:
550 pass
551 elif new_quotas == []:
552 g.hardcache.delete(key)
553 else:
554 g.hardcache.set(key, new_quotas, 86400 * 30)
555
556 return baskets
557
1d9b9fe Christopher Slowe * transparency updates to some of the pngs thanks to ytknows
KeyserSosa authored
558 # Needs to take the *canonicalized* version of each email
559 # When true, returns the reason
560 @classmethod
561 def which_emails_are_banned(cls, canons):
3437713 Roger Ostrander Email: Use non-hardcache ban fetching system
atiaxi authored
562 banned = hooks.get_hook('email.get_banned').call(canons=canons)
1d9b9fe Christopher Slowe * transparency updates to some of the pngs thanks to ytknows
KeyserSosa authored
563
3437713 Roger Ostrander Email: Use non-hardcache ban fetching system
atiaxi authored
564 # Create a dictionary like:
1d9b9fe Christopher Slowe * transparency updates to some of the pngs thanks to ytknows
KeyserSosa authored
565 # d["abc.def.com"] = [ "bob@abc.def.com", "sue@abc.def.com" ]
566 rv = {}
567 canons_by_domain = {}
3437713 Roger Ostrander Email: Use non-hardcache ban fetching system
atiaxi authored
568
569 # email.get_banned will return a list of lists (one layer from the
570 # hooks system, the second from the function itself); chain them
571 # together for easy processing
572 for canon in itertools.chain(*banned):
1d9b9fe Christopher Slowe * transparency updates to some of the pngs thanks to ytknows
KeyserSosa authored
573 rv[canon] = None
574
575 at_sign = canon.find("@")
576 domain = canon[at_sign+1:]
577 canons_by_domain.setdefault(domain, [])
578 canons_by_domain[domain].append(canon)
579
48dfdfc Roger Ostrander Domainban: Stop depending on zookeeper
atiaxi authored
580 # Hand off to the domain ban system; it knows in the case of
581 # abc@foo.bar.com to check foo.bar.com, bar.com, and .com
582 from r2.models.admintools import bans_for_domain_parts
583
1d9b9fe Christopher Slowe * transparency updates to some of the pngs thanks to ytknows
KeyserSosa authored
584 for domain, canons in canons_by_domain.iteritems():
48dfdfc Roger Ostrander Domainban: Stop depending on zookeeper
atiaxi authored
585 for d in bans_for_domain_parts(domain):
586 if d.no_email:
1d9b9fe Christopher Slowe * transparency updates to some of the pngs thanks to ytknows
KeyserSosa authored
587 rv[canon] = "domain"
588
589 return rv
590
003ca6e Brian Simpson Add AccountsByCanonicalEmail.
bsimpson63 authored
591 def set_email(self, email):
592 old_email = self.email
593 self.email = email
594 self._commit()
595 AccountsByCanonicalEmail.update_email(self, old_email, email)
596
1d9b9fe Christopher Slowe * transparency updates to some of the pngs thanks to ytknows
KeyserSosa authored
597 def has_banned_email(self):
598 canon = self.canonical_email()
599 which = self.which_emails_are_banned((canon,))
600 return which.get(canon, None)
601
d251ba7 Christopher Slowe * Improvements to the email verification system
KeyserSosa authored
602 def canonical_email(self):
76594cd Neil Williams Refactor and reorganize email canonicalization; add some tests.
spladug authored
603 return canonicalize_email(self.email)
d251ba7 Christopher Slowe * Improvements to the email verification system
KeyserSosa authored
604
605 def cromulent(self):
606 """Return whether the user has validated their email address and
607 passes some rudimentary 'not evil' checks."""
608
609 if not self.email_verified:
610 return False
611
1d9b9fe Christopher Slowe * transparency updates to some of the pngs thanks to ytknows
KeyserSosa authored
612 if self.has_banned_email():
d251ba7 Christopher Slowe * Improvements to the email verification system
KeyserSosa authored
613 return False
614
615 # Otherwise, congratulations; you're cromulent!
616 return True
617
e87f520 Christopher Slowe New Features:
KeyserSosa authored
618 def quota_limits(self, kind):
619 if kind != 'link':
620 raise NotImplementedError
621
d251ba7 Christopher Slowe * Improvements to the email verification system
KeyserSosa authored
622 if self.cromulent():
e87f520 Christopher Slowe New Features:
KeyserSosa authored
623 return dict(hour=3, day=10, week=50, month=150)
624 else:
625 return dict(hour=1, day=3, week=5, month=5)
626
627 def quota_full(self, kind):
628 limits = self.quota_limits(kind)
629 baskets = self.quota_baskets(kind)
630
631 if baskets is None:
632 return None
633
634 total = 0
635 filled_quota = None
636 for key in ('hour', 'day', 'week', 'month'):
637 total += len(baskets[key])
638 if total >= limits[key]:
639 filled_quota = key
640
641 return filled_quota
642
2869eaf david king New features:
ketralnis authored
643 @classmethod
e87f520 Christopher Slowe New Features:
KeyserSosa authored
644 def system_user(cls):
645 try:
646 return cls._by_name(g.system_user)
52da322 Christopher Slowe Bugfixes:
KeyserSosa authored
647 except (NotFound, AttributeError):
e87f520 Christopher Slowe New Features:
KeyserSosa authored
648 return None
a402d48 New features:
Mike authored
649
4631751 User option to block flair, and layout fixes.
Logan Hanks authored
650 def flair_enabled_in_sr(self, sr_id):
30c9134 Max Goodman Tweak flair preference lookup to not break on multi ids.
chromakode authored
651 return getattr(self, 'flair_%s_enabled' % sr_id, True)
4631751 User option to block flair, and layout fixes.
Logan Hanks authored
652
d6f2574 Brian Simpson Cleanup wrapped_flair.
bsimpson63 authored
653 def flair_text(self, sr_id):
654 return getattr(self, 'flair_%s_text' % sr_id, None)
655
656 def flair_css_class(self, sr_id):
657 return getattr(self, 'flair_%s_css_class' % sr_id, None)
658
7e22528 Jason Harvey Add Account method to update AccountActivityBySR.
alienth authored
659 def update_sr_activity(self, sr):
660 if not self._spam:
1520ddf Jason Harvey Rename AccountActivityBySR to AccountsActiveBySR.
alienth authored
661 AccountsActiveBySR.touch(self, sr)
7e22528 Jason Harvey Add Account method to update AccountActivityBySR.
alienth authored
662
fc9abd1 Keith Mitchell Award-claiming via one-time links.
kemitche authored
663 def get_trophy_id(self, uid):
664 '''Return the ID of the Trophy associated with the given "uid"
665
666 `uid` - The unique identifier for the Trophy to look up
667
668 '''
669 return getattr(self, 'received_trophy_%s' % uid, None)
670
671 def set_trophy_id(self, uid, trophy_id):
672 '''Recored that a user has received a Trophy with "uid"
673
674 `uid` - The trophy "type" that the user should only have one of
675 `trophy_id` - The ID of the corresponding Trophy object
676
677 '''
678 return setattr(self, 'received_trophy_%s' % uid, trophy_id)
679
75da617 Ricky Ramirez account: Add employee property.
rram authored
680 @property
681 def employee(self):
682 """Return if the user is an employee.
683
684 Being an employee grants them various special privileges.
685
686 """
dd925f4 Ricky Ramirez account: Protect employees against logged out users.
rram authored
687 return (hasattr(self, 'name') and
688 (self.name in g.admins or
689 self.name in g.sponsors or
690 self.name in g.employees))
75da617 Ricky Ramirez account: Add employee property.
rram authored
691
e394579 Brian Simpson Per user CPM overrides.
bsimpson63 authored
692 @property
9e20cc1 Jordan Milne Allow forcing HTTPS upon employees
JordanMilne authored
693 def https_forced(self):
694 """Return whether this account may only be used via HTTPS."""
25a890d Jordan Milne Replace `disable_require_employee_https` with a feature flag
JordanMilne authored
695 if feature.is_enabled_for("require_https", self):
9e20cc1 Jordan Milne Allow forcing HTTPS upon employees
JordanMilne authored
696 return True
697 return self.pref_force_https
698
699 @property
8aa2312 Jordan Milne Disable toolbar when using forcing HTTPS
JordanMilne authored
700 def uses_toolbar(self):
701 return not self.https_forced and self.pref_frame
702
703 @property
19d9968 Brian Simpson Don't PM users with gold subscription about expiration.
bsimpson63 authored
704 def has_gold_subscription(self):
210dd1b Brian Simpson Store stripe customer id in Account.gold_subscr_id.
bsimpson63 authored
705 return bool(getattr(self, 'gold_subscr_id', None))
706
707 @property
708 def has_paypal_subscription(self):
709 return (self.has_gold_subscription and
710 not self.gold_subscr_id.startswith('cus_'))
711
712 @property
713 def has_stripe_subscription(self):
714 return (self.has_gold_subscription and
715 self.gold_subscr_id.startswith('cus_'))
19d9968 Brian Simpson Don't PM users with gold subscription about expiration.
bsimpson63 authored
716
82b870d Chad Birch Gold: add preference to auto-renew with a creddit
Deimos authored
717 @property
718 def gold_will_autorenew(self):
719 return (self.has_gold_subscription or
720 (self.pref_creddit_autorenew and self.gold_creddits > 0))
721
e394579 Brian Simpson Per user CPM overrides.
bsimpson63 authored
722
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
723 class FakeAccount(Account):
724 _nodb = True
5ef76b9 Christopher Slowe New features:
KeyserSosa authored
725 pref_no_profanity = True
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
726
b06dc9b Max Goodman Add equality implementation for Account model.
chromakode authored
727 def __eq__(self, other):
728 return self is other
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
729
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
730 def valid_admin_cookie(cookie):
731 if g.read_only_mode:
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
732 return (False, None)
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
733
734 # parse the cookie
735 try:
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
736 first_login, last_request, hash = cookie.split(',')
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
737 except ValueError:
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
738 return (False, None)
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
739
740 # make sure it's a recent cookie
741 try:
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
742 first_login_time = datetime.strptime(first_login, COOKIE_TIMESTAMP_FORMAT)
743 last_request_time = datetime.strptime(last_request, COOKIE_TIMESTAMP_FORMAT)
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
744 except ValueError:
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
745 return (False, None)
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
746
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
747 cookie_age = datetime.utcnow() - first_login_time
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
748 if cookie_age.total_seconds() > g.ADMIN_COOKIE_TTL:
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
749 return (False, None)
750
751 idle_time = datetime.utcnow() - last_request_time
752 if idle_time.total_seconds() > g.ADMIN_COOKIE_MAX_IDLE:
753 return (False, None)
33b15bc Neil Williams Split the admin cookie out from the session cookie.
spladug authored
754
755 # validate
a42505c Neil Williams Keep admin cookie around if actively used.
spladug authored
756 expected_cookie = c.user.make_admin_cookie(first_login, last_request)
757 return (constant_time_compare(cookie, expected_cookie),
758 first_login)
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
759
760
8dfd73b Neil Williams Add framework for RFC-6238: Time-Based One Time Password Algorithm.
spladug authored
761 def valid_otp_cookie(cookie):
762 if g.read_only_mode:
763 return False
764
765 # parse the cookie
766 try:
767 remembered_at, signature = cookie.split(",")
768 except ValueError:
769 return False
770
771 # make sure it hasn't expired
772 try:
773 remembered_at_time = datetime.strptime(remembered_at, COOKIE_TIMESTAMP_FORMAT)
774 except ValueError:
775 return False
776
777 age = datetime.utcnow() - remembered_at_time
778 if age.total_seconds() > g.OTP_COOKIE_TTL:
779 return False
780
781 # validate
782 expected_cookie = c.user.make_otp_cookie(remembered_at)
783 return constant_time_compare(cookie, expected_cookie)
784
785
a402d48 New features:
Mike authored
786 def valid_feed(name, feedhash, path):
787 if name and feedhash and path:
788 from r2.lib.template_helpers import add_sr
789 path = add_sr(path)
790 try:
791 user = Account._by_name(name)
792 if (user.pref_private_feeds and
83058d4 Erik Karulf Use constant-time string comparison for auth.
ekarulf authored
793 constant_time_compare(feedhash, make_feedhash(user, path))):
a402d48 New features:
Mike authored
794 return user
795 except NotFound:
796 pass
797
798 def make_feedhash(user, path):
3366083 Neil Williams Create a vault for secret tokens and move some into it.
spladug authored
799 return hashlib.sha1("".join([user.name, user.password,
800 g.secrets["FEEDSECRET"]])
a402d48 New features:
Mike authored
801 ).hexdigest()
802
803 def make_feedurl(user, path, ext = "rss"):
804 u = UrlParser(path)
805 u.update_query(user = user.name,
806 feed = make_feedhash(user, path))
807 u.set_extension(ext)
808 return u.unparse()
809
722fbf2 Roger Ostrander Passwords: Enable checks against other passwords
atiaxi authored
810 def valid_password(a, password, compare_password=None):
a311805 Neil Williams Switch to bcrypt for password hashing.
spladug authored
811 # bail out early if the account or password's invalid
812 if not hasattr(a, 'name') or not hasattr(a, 'password') or not password:
813 return False
814
722fbf2 Roger Ostrander Passwords: Enable checks against other passwords
atiaxi authored
815 convert_password = False
816 if compare_password is None:
817 convert_password = True
818 compare_password = a.password
819
a311805 Neil Williams Switch to bcrypt for password hashing.
spladug authored
820 # standardize on utf-8 encoding
821 password = filters._force_utf8(password)
822
722fbf2 Roger Ostrander Passwords: Enable checks against other passwords
atiaxi authored
823 if compare_password.startswith('$2a$'):
a44f6f4 Neil Williams Upgrade passwords on log in when bcrypt work factor changed.
spladug authored
824 # it's bcrypt.
722fbf2 Roger Ostrander Passwords: Enable checks against other passwords
atiaxi authored
825 expected_hash = bcrypt.hashpw(password, compare_password)
826 if not constant_time_compare(compare_password, expected_hash):
a44f6f4 Neil Williams Upgrade passwords on log in when bcrypt work factor changed.
spladug authored
827 return False
a311805 Neil Williams Switch to bcrypt for password hashing.
spladug authored
828
a44f6f4 Neil Williams Upgrade passwords on log in when bcrypt work factor changed.
spladug authored
829 # if it's using the current work factor, we're done, but if it's not
830 # we'll have to rehash.
831 # the format is $2a$workfactor$salt+hash
722fbf2 Roger Ostrander Passwords: Enable checks against other passwords
atiaxi authored
832 work_factor = int(compare_password.split("$")[2])
a44f6f4 Neil Williams Upgrade passwords on log in when bcrypt work factor changed.
spladug authored
833 if work_factor == g.bcrypt_work_factor:
834 return a
835 else:
836 # alright, so it's not bcrypt. how old is it?
837 # if the length of the stored hash is 43 bytes, the sha-1 hash has a salt
838 # otherwise it's sha-1 with no salt.
839 salt = ''
722fbf2 Roger Ostrander Passwords: Enable checks against other passwords
atiaxi authored
840 if len(compare_password) == 43:
841 salt = compare_password[:3]
a44f6f4 Neil Williams Upgrade passwords on log in when bcrypt work factor changed.
spladug authored
842 expected_hash = passhash(a.name, password, salt)
843
722fbf2 Roger Ostrander Passwords: Enable checks against other passwords
atiaxi authored
844 if not constant_time_compare(compare_password, expected_hash):
a44f6f4 Neil Williams Upgrade passwords on log in when bcrypt work factor changed.
spladug authored
845 return False
a311805 Neil Williams Switch to bcrypt for password hashing.
spladug authored
846
847 # since we got this far, it's a valid password but in an old format
848 # let's upgrade it
722fbf2 Roger Ostrander Passwords: Enable checks against other passwords
atiaxi authored
849 if convert_password:
850 a.password = bcrypt_password(password)
851 a._commit()
a311805 Neil Williams Switch to bcrypt for password hashing.
spladug authored
852 return a
853
854 def bcrypt_password(password):
855 salt = bcrypt.gensalt(log_rounds=g.bcrypt_work_factor)
856 return bcrypt.hashpw(password, salt)
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
857
858 def passhash(username, password, salt = ''):
859 if salt is True:
860 salt = randstr(3)
861 tohash = '%s%s %s' % (salt, username, password)
c9c65dd Neil Williams Replace references to deprecated sha module with hashlib.
spladug authored
862 return salt + hashlib.sha1(tohash).hexdigest()
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
863
c852953 fixed recover password
shuffman authored
864 def change_password(user, newpassword):
a311805 Neil Williams Switch to bcrypt for password hashing.
spladug authored
865 user.password = bcrypt_password(newpassword)
c852953 fixed recover password
shuffman authored
866 user._commit()
abf6f6e Jason Harvey Store a LastModified update on account password change.
alienth authored
867 LastModified.touch(user._fullname, 'Password')
c852953 fixed recover password
shuffman authored
868 return True
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
869
870 #TODO reset the cache
b55934a Neil Williams Account: ensure all non-defaulted attributes are atomically created.
spladug authored
871 def register(name, password, registration_ip):
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
872 try:
873 a = Account._by_name(name)
874 raise AccountExists
875 except NotFound:
876 a = Account(name = name,
a311805 Neil Williams Switch to bcrypt for password hashing.
spladug authored
877 password = bcrypt_password(password))
5ef76b9 Christopher Slowe New features:
KeyserSosa authored
878 # new accounts keep the profanity filter settings until opting out
879 a.pref_no_profanity = True
b55934a Neil Williams Account: ensure all non-defaulted attributes are atomically created.
spladug authored
880 a.registration_ip = registration_ip
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
881 a._commit()
e35b519 * removed references to clear_memo
spez authored
882
883 #clear the caches
884 Account._by_name(name, _update = True)
885 Account._by_name(name, allow_deleted = True, _update = True)
4778b17 Christopher Slowe initial checkin
KeyserSosa authored
886 return a
887
888 class Friend(Relation(Account, Account)): pass
bf9f43c Christopher Slowe Messaging/commenting
KeyserSosa authored
889
4d7a2fa Keith Mitchell Allow users to block users that harass them
kemitche authored
890 Account.__bases__ += (UserRel('friend', Friend, disable_reverse_ids_fn=True),
6c10091 Keith Mitchell Reverse lookup of blocked user
kemitche authored
891 UserRel('enemy', Friend, disable_reverse_ids_fn=False))
4440ccf Christopher Slowe overhaul of JS and form handling code, not based on jQuery
KeyserSosa authored
892
893 class DeletedUser(FakeAccount):
894 @property
895 def name(self):
896 return '[deleted]'
897
a402d48 New features:
Mike authored
898 @property
899 def _deleted(self):
900 return True
901
4440ccf Christopher Slowe overhaul of JS and form handling code, not based on jQuery
KeyserSosa authored
902 def _fullname(self):
903 raise NotImplementedError
904
905 def _id(self):
906 raise NotImplementedError
a402d48 New features:
Mike authored
907
908 def __setattr__(self, attr, val):
909 if attr == '_deleted':
910 pass
911 else:
912 object.__setattr__(self, attr, val)
2fe83ed Jason Harvey Add AccountActivityBySR.
alienth authored
913
1520ddf Jason Harvey Rename AccountActivityBySR to AccountsActiveBySR.
alienth authored
914 class AccountsActiveBySR(tdb_cassandra.View):
2fe83ed Jason Harvey Add AccountActivityBySR.
alienth authored
915 _use_db = True
916 _connection_pool = 'main'
6bfa552 Keith Mitchell tdb_cassandra: Magic up the _ttl attribute for ThingMeta users
kemitche authored
917 _ttl = timedelta(minutes=15)
2fe83ed Jason Harvey Add AccountActivityBySR.
alienth authored
918
919 _extra_schema_creation_args = dict(key_validation_class=ASCII_TYPE)
920
921 _read_consistency_level = tdb_cassandra.CL.ONE
922 _write_consistency_level = tdb_cassandra.CL.ANY
923
924 @classmethod
925 def touch(cls, account, sr):
926 cls._set_values(sr._id36,
927 {account._id36: ''})
928
929 @classmethod
49607b7 Jason Harvey Add a memoized method of get_count, and use it by default.
alienth authored
930 def get_count(cls, sr, cached=True):
931 return cls.get_count_cached(sr._id36, _update=not cached)
932
933 @classmethod
934 @memoize('accounts_active', time=60)
935 def get_count_cached(cls, sr_id):
936 return cls._cf.get_count(sr_id)
d2ccc40 Neil Williams Automatically delete password hashes of deleted accounts.
spladug authored
937
938
ff5e5e0 Brian Simpson Let users block messages from subreddits.
bsimpson63 authored
939 class BlockedSubredditsByAccount(tdb_cassandra.DenormalizedRelation):
940 _use_db = True
941 _last_modified_name = 'block_subreddit'
942 _read_consistency_level = tdb_cassandra.CL.QUORUM
943 _write_consistency_level = tdb_cassandra.CL.QUORUM
944 _connection_pool = 'main'
945 _views = []
946
947 @classmethod
948 def value_for(cls, thing1, thing2):
949 return ''
950
951 @classmethod
952 def block(cls, user, sr):
953 cls.create(user, sr)
954
955 @classmethod
956 def unblock(cls, user, sr):
957 cls.destroy(user, sr)
958
959 @classmethod
960 def is_blocked(cls, user, sr):
961 try:
df77799 Brian Simpson BlockedSubredditsByAccount: fix is_blocked method.
bsimpson63 authored
962 r = cls.fast_query(user, [sr])
ff5e5e0 Brian Simpson Let users block messages from subreddits.
bsimpson63 authored
963 except tdb_cassandra.NotFound:
964 return False
df77799 Brian Simpson BlockedSubredditsByAccount: fix is_blocked method.
bsimpson63 authored
965 return (user, sr) in r
ff5e5e0 Brian Simpson Let users block messages from subreddits.
bsimpson63 authored
966
967
d2ccc40 Neil Williams Automatically delete password hashes of deleted accounts.
spladug authored
968 @trylater_hooks.on("trylater.account_deletion")
969 def on_account_deletion(mature_items):
970 for account_id36 in mature_items.itervalues():
971 account = Account._byID36(account_id36, data=True)
972
973 if not account._deleted:
974 continue
975
976 account.password = ""
977 account._commit()
003ca6e Brian Simpson Add AccountsByCanonicalEmail.
bsimpson63 authored
978
979
980 class AccountsByCanonicalEmail(tdb_cassandra.View):
981 __metaclass__ = tdb_cassandra.ThingMeta
982
983 _use_db = True
984 _compare_with = tdb_cassandra.UTF8_TYPE
985 _extra_schema_creation_args = dict(
986 key_validation_class=tdb_cassandra.UTF8_TYPE,
987 )
988
989 @classmethod
990 def update_email(cls, account, old, new):
991 old, new = map(canonicalize_email, (old, new))
992
993 if old == new:
994 return
995
996 with cls._cf.batch() as b:
997 if old:
998 b.remove(old, {account._id36: ""})
999 if new:
1000 b.insert(new, {account._id36: ""})
1001
1002 @classmethod
1003 def get_accounts(cls, email_address):
1004 canonical = canonicalize_email(email_address)
1005 if not canonical:
1006 return []
1007 account_id36s = cls.get_time_sorted_columns(canonical).keys()
1008 return Account._byID36(account_id36s, data=True, return_dict=False)
Something went wrong with that request. Please try again.