Skip to content
Newer
Older
100644 1091 lines (898 sloc) 37.3 KB
df971c3 @spez fix the licenses so they stop polluting all the other diffs
spez authored Sep 17, 2008
1 # The contents of this file are subject to the Common Public Attribution
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 17, 2008
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 @ketralnis New features:
ketralnis authored May 3, 2010
9 #
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
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 @ketralnis New features:
ketralnis authored May 3, 2010
13 #
914b949 @spladug Update / add license headers.
spladug authored Jun 19, 2012
14 # The Original Code is reddit.
2869eaf @ketralnis New features:
ketralnis authored May 3, 2010
15 #
914b949 @spladug Update / add license headers.
spladug authored Jun 19, 2012
16 # The Original Developer is the Initial Developer. The Initial Developer of
17 # the Original Code is reddit Inc.
2869eaf @ketralnis New features:
ketralnis authored May 3, 2010
18 #
af09fa8 @spladug Update license headers to 2015.
spladug authored Jan 7, 2015
19 # All portions of the code written by reddit are Copyright (c) 2006-2015 reddit
914b949 @spladug Update / add license headers.
spladug authored Jun 19, 2012
20 # Inc. All Rights Reserved.
21 ###############################################################################
22
c155eee @Deimos Stop handling 304s for comments pages/userpages
Deimos authored Sep 14, 2015
23 import random
24
25a890d @JordanMilne Replace `disable_require_employee_https` with a feature flag
JordanMilne authored Aug 15, 2014
25 from r2.config import feature
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
26 from r2.lib.db.thing import Thing, Relation, NotFound
27 from r2.lib.db.operators import lower
28 from r2.lib.db.userrel import UserRel
2fe83ed @alienth Add AccountActivityBySR.
alienth authored Aug 12, 2012
29 from r2.lib.db import tdb_cassandra
e35b519 @spez * removed references to clear_memo
spez authored Mar 4, 2009
30 from r2.lib.memoize import memoize
8a5f685 @chromakode Move private-shadowed utils into separate module.
chromakode authored Apr 16, 2014
31 from r2.lib.utils import randstr, timefromnow
2243af7 @spladug Clean out dead last-modified rel code.
spladug authored Jun 11, 2012
32 from r2.lib.utils import UrlParser
76594cd @spladug Refactor and reorganize email canonicalization; add some tests.
spladug authored Dec 21, 2012
33 from r2.lib.utils import constant_time_compare, canonicalize_email
0bceab8 @JordanMilne Add queue to scrub deleted accounts of potential leaks
JordanMilne authored Jan 16, 2015
34 from r2.lib import amqp, filters, hooks
37e2ba9 @ketralnis * Combine cassandra clusters into a single one
ketralnis authored Oct 18, 2010
35 from r2.lib.log import log_text
0407f4f @xiongchiamiov Refactor ban models
xiongchiamiov authored Sep 10, 2015
36 from r2.models.bans import TempTimeout
555c02a @spladug Dual-write last visit timestamp.
spladug authored May 24, 2012
37 from r2.models.last_modified import LastModified
b24ba98 @Deimos Refactor flair API endpoints
Deimos authored Mar 2, 2015
38 from r2.models.modaction import ModAction
d2ccc40 @spladug Automatically delete password hashes of deleted accounts.
spladug authored Feb 10, 2014
39 from r2.models.trylater import TryLater
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
40
d990533 @bsimpson63 Upgrade pylons from 0.9.7 to 1.0.
bsimpson63 authored Aug 17, 2015
41 from pylons import request
42 from pylons import tmpl_context as c
43 from pylons import app_globals as g
88d191e @spladug Gold feature: personal karma breakdown by subreddit.
spladug authored Dec 24, 2011
44 from pylons.i18n import _
c9c65dd @spladug Replace references to deprecated sha module with hashlib.
spladug authored Jun 15, 2012
45 import time
46 import hashlib
b8d9981 @Deimos Refactor ProfileBar and Account.all_karmas
Deimos authored Aug 3, 2014
47 from collections import Counter, OrderedDict
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
48 from copy import copy
bf9f43c @KeyserSosa Messaging/commenting
KeyserSosa authored Dec 1, 2009
49 from datetime import datetime, timedelta
a311805 @spladug Switch to bcrypt for password hashing.
spladug authored Oct 20, 2011
50 import bcrypt
33b15bc @spladug Split the admin cookie out from the session cookie.
spladug authored Mar 12, 2012
51 import hmac
52 import hashlib
2fe83ed @alienth Add AccountActivityBySR.
alienth authored Aug 13, 2012
53 from pycassa.system_manager import ASCII_TYPE
33b15bc @spladug Split the admin cookie out from the session cookie.
spladug authored Mar 12, 2012
54
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
55
d2ccc40 @spladug Automatically delete password hashes of deleted accounts.
spladug authored Feb 10, 2014
56 trylater_hooks = hooks.HookRegistrar()
a42505c @spladug Keep admin cookie around if actively used.
spladug authored Mar 14, 2012
57 COOKIE_TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S'
58
59
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
60 class AccountExists(Exception): pass
61
62 class Account(Thing):
63 _data_int_props = Thing._data_int_props + ('link_karma', 'comment_karma',
64 'report_made', 'report_correct',
65 'report_ignored', 'spammer',
09c98d6 @umbrae Inbox counts: Add dark unread counts badge, start writing to inbox_count
umbrae authored Aug 8, 2014
66 'reported', 'gold_creddits',
67 'inbox_count',
b76322f @bsimpson63 Add alerts for suspicious selfserve advertising payments.
bsimpson63 authored Oct 30, 2014
68 'num_payment_methods',
69 'num_failed_payments',
7d91d02 @MelissaCole Add num_gildings to keep track of gildings count
MelissaCole authored May 4, 2015
70 'num_gildings',
35e3997 @weffey Takedown tool
weffey authored Apr 15, 2015
71 'admin_takedown_strikes',
09c98d6 @umbrae Inbox counts: Add dark unread counts badge, start writing to inbox_count
umbrae authored Aug 8, 2014
72 )
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
73 _int_prop_suffix = '_karma'
9a4271f @KeyserSosa Upgrade Instructions
KeyserSosa authored Jun 15, 2010
74 _essentials = ('name', )
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
75 _defaults = dict(pref_numsites = 25,
76 pref_newwindow = False,
ae9eaf5 @spez add preference to remove recently clicked widget
spez authored Jun 3, 2009
77 pref_clickgadget = 5,
ff002a4 @ketralnis Add backend for new gold feature: "remember my visits".
ketralnis authored Oct 24, 2013
78 pref_store_visits = False,
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
79 pref_public_votes = False,
1b030c3 @raugturi Added option to hide user profile from robots.
raugturi authored Jun 15, 2011
80 pref_hide_from_robots = False,
37e2ba9 @ketralnis * Combine cassandra clusters into a single one
ketralnis authored Oct 18, 2010
81 pref_research = False,
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
82 pref_hide_ups = False,
4440ccf @KeyserSosa overhaul of JS and form handling code, not based on jQuery
KeyserSosa authored Jan 26, 2009
83 pref_hide_downs = False,
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
84 pref_min_link_score = -4,
85 pref_min_comment_score = -4,
86 pref_num_comments = g.num_comments,
c47c697 @umbrae Add optional controversial indicator to comment scores
umbrae authored Jun 22, 2014
87 pref_highlight_controversial=False,
d5f1912 @xiongchiamiov Revert "Comment sort preference: dual-write to migrate"
xiongchiamiov authored May 20, 2015
88 pref_default_comment_sort = 'confidence',
0745f5b @KeyserSosa make 1/2 of the default reddits English reddits if the user hasn't se…
KeyserSosa authored May 13, 2009
89 pref_lang = g.lang,
90 pref_content_langs = (g.lang,),
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
91 pref_over_18 = False,
92 pref_compress = False,
7e6cbc4 @Deimos Domain area: add new user pref to show extra info
Deimos authored May 29, 2013
93 pref_domain_details = False,
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
94 pref_organic = True,
08c431b @KeyserSosa * Comply with the spec on 304 errors so Chrome won't barf download.g…
KeyserSosa authored May 28, 2010
95 pref_no_profanity = True,
5ef76b9 @KeyserSosa New features:
KeyserSosa authored May 3, 2010
96 pref_label_nsfw = True,
6bcef00 @ketralnis 1. Allow a reddit to have a cname, like www.proggit.com, that renders
ketralnis authored Aug 26, 2008
97 pref_show_stylesheets = True,
49912e6 @MelissaCole Pull reddit themes in pref from wiki
MelissaCole authored Apr 17, 2015
98 pref_enable_default_themes=False,
99 pref_default_theme_sr=None,
4194ced Add a user preference for whether to show flair.
Logan Hanks authored Oct 25, 2011
100 pref_show_flair = True,
13e191b Add a user preference for showing link flair.
Logan Hanks authored Apr 17, 2012
101 pref_show_link_flair = True,
5ef76b9 @KeyserSosa New features:
KeyserSosa authored May 3, 2010
102 pref_mark_messages_read = True,
2869eaf @ketralnis New features:
ketralnis authored May 3, 2010
103 pref_threaded_messages = True,
104 pref_collapse_read_messages = False,
9c6aa71 @xiongchiamiov Add option for sending orangereds as emails
xiongchiamiov authored Oct 24, 2014
105 pref_email_messages = False,
a402d48 New features:
Mike authored May 3, 2010
106 pref_private_feeds = True,
b70556a @JordanMilne Add support for forced HTTPS with HSTS grants
JordanMilne authored Jul 1, 2014
107 pref_force_https = False,
a30cc40 @MelissaCole Consolidate gold ad preferences into one
MelissaCole authored Apr 22, 2015
108 pref_hide_ads = False,
a453758 @umbrae Add a preference for trending on the front page
umbrae authored Apr 10, 2014
109 pref_show_trending=True,
37e2ba9 @ketralnis * Combine cassandra clusters into a single one
ketralnis authored Oct 18, 2010
110 pref_highlight_new_comments = True,
e057278 @spladug Gold Feature: "The Butler". Username monitoring in comments.
spladug authored May 10, 2013
111 pref_monitor_mentions=True,
07366ac @chromakode Move left bar collapsing to server-side preference.
chromakode authored Aug 30, 2013
112 pref_collapse_left_bar=False,
8c9b31d @bsimpson63 ServerSecondsBar can be made public.
bsimpson63 authored Oct 8, 2013
113 pref_public_server_seconds=False,
cc648c7 @umbrae Suggested sort: Add preference to ignore suggested sorts
umbrae authored Apr 22, 2015
114 pref_ignore_suggested_sort=False,
03bc77b @umbrae Beta mode: Add preference and subreddit callouts
umbrae authored May 1, 2015
115 pref_beta=False,
8f9f171 @madbook Search: Add pref to enable legacy search.
madbook authored Jul 5, 2015
116 pref_legacy_search=False,
9a4271f @KeyserSosa Upgrade Instructions
KeyserSosa authored Jun 16, 2010
117 mobile_compress = False,
118 mobile_thumbnail = True,
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
119 reported = 0,
120 report_made = 0,
121 report_correct = 0,
122 report_ignored = 0,
123 spammer = 0,
124 sort_options = {},
950971c added last modified to profile/comment pages
shuffman authored Jun 28, 2008
125 has_subscribed = False,
0c04094 @KeyserSosa updates to RSS feed to link to permalink page instead of goto. Also a…
KeyserSosa authored Aug 28, 2008
126 pref_media = 'subreddit',
603ec0e @rram wiki: Fix bug with wiki permissions.
rram authored Nov 21, 2012
127 wiki_override = None,
bf9f43c @KeyserSosa Messaging/commenting
KeyserSosa authored Dec 1, 2009
128 email = "",
e87f520 @KeyserSosa New Features:
KeyserSosa authored May 4, 2010
129 email_verified = False,
bf9f43c @KeyserSosa Messaging/commenting
KeyserSosa authored Dec 1, 2009
130 ignorereports = False,
0ae8f2f @ketralnis 21 Jul 2010 merge
ketralnis authored Jul 21, 2010
131 pref_show_promote = None,
132 gold = False,
37e2ba9 @ketralnis * Combine cassandra clusters into a single one
ketralnis authored Oct 18, 2010
133 gold_charter = False,
7fff900 @ketralnis February 2011 Merge
ketralnis authored Feb 23, 2011
134 gold_creddits = 0,
7d91d02 @MelissaCole Add num_gildings to keep track of gildings count
MelissaCole authored May 5, 2015
135 num_gildings=0,
acd485e @spladug Add default for new cake system.
spladug authored Sep 19, 2013
136 cake_expiration=None,
8dfd73b @spladug Add framework for RFC-6238: Time-Based One Time Password Algorithm.
spladug authored Jul 22, 2012
137 otp_secret=None,
65b1467 Backend support for bans.
Roger Ostrander authored Oct 16, 2012
138 state=0,
5e249f4 @spladug Make all moderators have a modmsgtime attribute.
spladug authored Jan 8, 2014
139 modmsgtime=None,
09c98d6 @umbrae Inbox counts: Add dark unread counts badge, start writing to inbox_count
umbrae authored Aug 8, 2014
140 inbox_count=0,
594b6bf Listingcontroller: Optionally display banned userpages
Roger Ostrander authored Jun 4, 2014
141 banned_profile_visible=False,
ac83646 @bsimpson63 Add a notice when localized default subreddits are used.
bsimpson63 authored Jun 11, 2014
142 pref_use_global_defaults=False,
143 pref_hide_locationbar=False,
82b870d @Deimos Gold: add preference to auto-renew with a creddit
Deimos authored Aug 4, 2014
144 pref_creddit_autorenew=False,
f963cb7 @Deimos Support certain accounts never adding to sent
Deimos authored Sep 22, 2014
145 update_sent_messages=True,
0055f9a @MelissaCole Can reveal identity or write a message in gildings
MelissaCole authored Dec 15, 2014
146 num_payment_methods=0,
147 num_failed_payments=0,
148 pref_show_snoovatar=False,
149 gild_reveal_username=False,
dbb7935 @bsimpson63 Enable per-user global selfserve cpm override.
bsimpson63 authored Jan 16, 2015
150 selfserve_cpm_override_pennies=None,
55c22f9 @MelissaCole Display gold expiration for users
MelissaCole authored Apr 22, 2015
151 pref_show_gold_expiration=False,
35e3997 @weffey Takedown tool
weffey authored Apr 15, 2015
152 admin_takedown_strikes=0,
871d50c @bsimpson63 Allow users to choose threaded view for modmail.
bsimpson63 authored Jul 22, 2015
153 pref_threaded_modmail=False,
191df00 @xiongchiamiov Timeouts: add restrictions
xiongchiamiov authored Aug 7, 2015
154 in_timeout=False,
e86333e @dellis23 mobile: Gild the first login on an official mobile app.
dellis23 authored Mar 8, 2016
155 has_used_mobile_app=False,
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
156 )
c413a2e @kemitche Add PATCH /api/v1/me/prefs endpoint
kemitche authored Mar 27, 2014
157 _preference_attrs = tuple(k for k in _defaults.keys()
158 if k.startswith("pref_"))
159
160 def preferences(self):
161 return {pref: getattr(self, pref) for pref in self._preference_attrs}
950971c added last modified to profile/comment pages
shuffman authored Jun 28, 2008
162
b06dc9b @chromakode Add equality implementation for Account model.
chromakode authored Sep 14, 2012
163 def __eq__(self, other):
164 if type(self) != type(other):
165 return False
166
167 return self._id == other._id
168
169 def __ne__(self, other):
170 return not self.__eq__(other)
171
e8e751a @spladug Don't spam ban messages for users who don't care.
spladug authored Apr 20, 2012
172 def has_interacted_with(self, sr):
495d761 @Deimos Track subreddit participation directly
Deimos authored May 15, 2015
173 try:
174 r = SubredditParticipationByAccount.fast_query(self, [sr])
175 except tdb_cassandra.NotFound:
e8e751a @spladug Don't spam ban messages for users who don't care.
spladug authored Apr 20, 2012
176 return False
177
495d761 @Deimos Track subreddit participation directly
Deimos authored May 15, 2015
178 return (self, sr) in r
e8e751a @spladug Don't spam ban messages for users who don't care.
spladug authored Apr 20, 2012
179
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
180 def karma(self, kind, sr = None):
181 suffix = '_' + kind + '_karma'
e87f520 @KeyserSosa New Features:
KeyserSosa authored May 4, 2010
182
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
183 #if no sr, return the sum
184 if sr is None:
185 total = 0
186 for k, v in self._t.iteritems():
187 if k.endswith(suffix):
188 total += v
189 return total
190 else:
191 try:
192 return getattr(self, sr.name + suffix)
193 except AttributeError:
194 #if positive karma elsewhere, you get min_up_karma
195 if self.karma(kind) > 0:
196 return g.MIN_UP_KARMA
197 else:
198 return 0
199
200 def incr_karma(self, kind, sr, amt):
37e2ba9 @ketralnis * Combine cassandra clusters into a single one
ketralnis authored Oct 18, 2010
201 if sr.name.startswith('_'):
202 g.log.info("Ignoring karma increase for subreddit %r" % (sr.name,))
203 return
204
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
205 prop = '%s_%s_karma' % (sr.name, kind)
206 if hasattr(self, prop):
207 return self._incr(prop, amt)
208 else:
209 default_val = self.karma(kind, sr)
210 setattr(self, prop, default_val + amt)
211 self._commit()
212
213 @property
214 def link_karma(self):
215 return self.karma('link')
216
217 @property
218 def comment_karma(self):
219 return self.karma('comment')
220
a635c9c @andre-d karma: Add karma list api endpoint.
andre-d authored Apr 3, 2014
221 def all_karmas(self, include_old=True):
b8d9981 @Deimos Refactor ProfileBar and Account.all_karmas
Deimos authored Aug 3, 2014
222 """Get all of the user's subreddit-specific karma totals.
223
224 Returns an OrderedDict keyed on subreddit name and containing
225 (link_karma, comment_karma) tuples, ordered by the combined total
226 descending.
227 """
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
228 link_suffix = '_link_karma'
229 comment_suffix = '_comment_karma'
b8d9981 @Deimos Refactor ProfileBar and Account.all_karmas
Deimos authored Aug 3, 2014
230
231 comment_karmas = Counter()
232 link_karmas = Counter()
233 combined_karmas = Counter()
234
235 for key, value in self._t.iteritems():
236 if key.endswith(link_suffix):
237 sr_name = key[:-len(link_suffix)]
238 link_karmas[sr_name] = value
239 elif key.endswith(comment_suffix):
240 sr_name = key[:-len(comment_suffix)]
241 comment_karmas[sr_name] = value
242 else:
243 continue
244
245 combined_karmas[sr_name] += value
246
247 all_karmas = OrderedDict()
248 for sr_name, total in combined_karmas.most_common():
249 all_karmas[sr_name] = (link_karmas[sr_name],
250 comment_karmas[sr_name])
251
252 if include_old:
253 old_link_karma = self._t.get('link_karma', 0)
254 old_comment_karma = self._t.get('comment_karma', 0)
255 if old_link_karma or old_comment_karma:
256 all_karmas['ancient history'] = (old_link_karma,
257 old_comment_karma)
258
259 return all_karmas
bf9f43c @KeyserSosa Messaging/commenting
KeyserSosa authored Dec 1, 2009
260
261 def update_last_visit(self, current_time):
262 from admintools import apply_updates
263
a7c74d7 @bsimpson63 Account.update_last_visit: Add timer.
bsimpson63 authored Sep 26, 2015
264 timer = g.stats.get_timer("account.update_last_visit")
265 timer.start()
266
267 apply_updates(self, timer)
bf9f43c @KeyserSosa Messaging/commenting
KeyserSosa authored Dec 1, 2009
268
14cb34c @spladug LastModified: cut reads for Last Visit over to new schema.
spladug authored Jun 6, 2012
269 prev_visit = LastModified.get(self._fullname, "Visit")
a7c74d7 @bsimpson63 Account.update_last_visit: Add timer.
bsimpson63 authored Sep 26, 2015
270 timer.intermediate("get_last_modified")
271
14cb34c @spladug LastModified: cut reads for Last Visit over to new schema.
spladug authored Jun 7, 2012
272 if prev_visit and current_time - prev_visit < timedelta(days=1):
5d45416 @bsimpson63 update_last_visit timer: Use stop/intermediate correctly.
bsimpson63 authored Sep 28, 2015
273 timer.intermediate("set_last_modified.noop")
274 timer.stop()
bf9f43c @KeyserSosa Messaging/commenting
KeyserSosa authored Dec 1, 2009
275 return
276
555c02a @spladug Dual-write last visit timestamp.
spladug authored May 25, 2012
277 LastModified.touch(self._fullname, "Visit")
5d45416 @bsimpson63 update_last_visit timer: Use stop/intermediate correctly.
bsimpson63 authored Sep 29, 2015
278 timer.intermediate("set_last_modified.done")
279 timer.stop()
b71d8bf @alienth Store a last_visit time on the Account thing.
alienth authored Aug 10, 2012
280
33b15bc @spladug Split the admin cookie out from the session cookie.
spladug authored Mar 12, 2012
281 def make_cookie(self, timestr=None):
a42505c @spladug Keep admin cookie around if actively used.
spladug authored Mar 14, 2012
282 timestr = timestr or time.strftime(COOKIE_TIMESTAMP_FORMAT)
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
283 id_time = str(self._id) + ',' + timestr
3366083 @spladug Create a vault for secret tokens and move some into it.
spladug authored Nov 15, 2013
284 to_hash = ','.join((id_time, self.password, g.secrets["SECRET"]))
c9c65dd @spladug Replace references to deprecated sha module with hashlib.
spladug authored Jun 16, 2012
285 return id_time + ',' + hashlib.sha1(to_hash).hexdigest()
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
286
a42505c @spladug Keep admin cookie around if actively used.
spladug authored Mar 14, 2012
287 def make_admin_cookie(self, first_login=None, last_request=None):
288 first_login = first_login or datetime.utcnow().strftime(COOKIE_TIMESTAMP_FORMAT)
289 last_request = last_request or datetime.utcnow().strftime(COOKIE_TIMESTAMP_FORMAT)
290 hashable = ','.join((first_login, last_request, request.ip, request.user_agent, self.password))
3366083 @spladug Create a vault for secret tokens and move some into it.
spladug authored Nov 15, 2013
291 mac = hmac.new(g.secrets["SECRET"], hashable, hashlib.sha1).hexdigest()
a42505c @spladug Keep admin cookie around if actively used.
spladug authored Mar 14, 2012
292 return ','.join((first_login, last_request, mac))
33b15bc @spladug Split the admin cookie out from the session cookie.
spladug authored Mar 12, 2012
293
8dfd73b @spladug Add framework for RFC-6238: Time-Based One Time Password Algorithm.
spladug authored Jul 22, 2012
294 def make_otp_cookie(self, timestamp=None):
295 timestamp = timestamp or datetime.utcnow().strftime(COOKIE_TIMESTAMP_FORMAT)
296 secrets = [request.user_agent, self.otp_secret, self.password]
3366083 @spladug Create a vault for secret tokens and move some into it.
spladug authored Nov 15, 2013
297 signature = hmac.new(g.secrets["SECRET"], ','.join([timestamp] + secrets), hashlib.sha1).hexdigest()
8dfd73b @spladug Add framework for RFC-6238: Time-Based One Time Password Algorithm.
spladug authored Jul 22, 2012
298
299 return ",".join((timestamp, signature))
300
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
301 def needs_captcha(self):
9c8be78 @Deimos Account.needs_captcha: add hook, use live_config
Deimos authored Feb 23, 2015
302 if g.disable_captcha:
303 return False
304
305 hook = hooks.get_hook("account.is_captcha_exempt")
306 captcha_exempt = hook.call_until_return(account=self)
307 if captcha_exempt:
308 return False
309
310 if self.link_karma >= g.live_config["captcha_exempt_link_karma"]:
311 return False
312
313 if self.comment_karma >= g.live_config["captcha_exempt_comment_karma"]:
314 return False
315
316 return True
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
317
165ca78 @Deimos Create subreddit: add support for min age/karma
Deimos authored Feb 23, 2015
318 @property
319 def can_create_subreddit(self):
320 hook = hooks.get_hook("account.can_create_subreddit")
321 can_create = hook.call_until_return(account=self)
322 if can_create is not None:
323 return can_create
324
325 min_age = timedelta(days=g.live_config["create_sr_account_age_days"])
326 if self._age < min_age:
327 return False
328
329 if (self.link_karma < g.live_config["create_sr_link_karma"] and
330 self.comment_karma < g.live_config["create_sr_comment_karma"]):
331 return False
332
333 return True
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
334
335 @classmethod
336 @memoize('account._by_name')
2f29981 @bsimpson63 Account._by_name_cache: Don't pollute cache with unloaded objects
bsimpson63 authored Jan 26, 2016
337 def _by_name_cache(cls, name, allow_deleted=False):
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
338 #relower name here, just in case
339 deleted = (True, False) if allow_deleted else False
2f29981 @bsimpson63 Account._by_name_cache: Don't pollute cache with unloaded objects
bsimpson63 authored Jan 26, 2016
340 q = cls._query(
341 lower(cls.c.name) == name.lower(),
342 cls.c._spam == (True, False),
343 cls.c._deleted == deleted,
344 data=True,
345 )
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
346
347 q._limit = 1
348 l = list(q)
349 if l:
350 return l[0]._id
351
352 @classmethod
e35b519 @spez * removed references to clear_memo
spez authored Mar 4, 2009
353 def _by_name(cls, name, allow_deleted = False, _update = False):
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
354 #lower name here so there is only one cache
e35b519 @spez * removed references to clear_memo
spez authored Mar 4, 2009
355 uid = cls._by_name_cache(name.lower(), allow_deleted, _update = _update)
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
356 if uid:
2f29981 @bsimpson63 Account._by_name_cache: Don't pollute cache with unloaded objects
bsimpson63 authored Jan 26, 2016
357 return cls._byID(uid, data=True)
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
358 else:
359 raise NotFound, 'Account %s' % name
360
ee5ea8c @umbrae Inbox_counts: corrections on deletes, spams, edits
umbrae authored Aug 22, 2014
361 @classmethod
362 def _names_to_ids(cls, names, ignore_missing=False, allow_deleted=False,
363 _update=False):
364 for name in names:
365 uid = cls._by_name_cache(name.lower(), allow_deleted, _update=_update)
366 if not uid:
367 if ignore_missing:
368 continue
369 raise NotFound('Account %s' % name)
370 yield uid
371
bf9f43c @KeyserSosa Messaging/commenting
KeyserSosa authored Dec 1, 2009
372 # Admins only, since it's not memoized
373 @classmethod
374 def _by_name_multiple(cls, name):
375 q = cls._query(lower(Account.c.name) == name.lower(),
376 Account.c._spam == (True, False),
377 Account.c._deleted == (True, False))
378 return list(q)
379
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
380 @property
381 def friends(self):
382 return self.friend_ids()
383
4d7a2fa @kemitche Allow users to block users that harass them
kemitche authored Jun 24, 2011
384 @property
385 def enemies(self):
386 return self.enemy_ids()
387
5e249f4 @spladug Make all moderators have a modmsgtime attribute.
spladug authored Jan 9, 2014
388 @property
389 def is_moderator_somewhere(self):
390 # modmsgtime can be:
391 # - a date: the user is a mod somewhere and has unread modmail
392 # - False: the user is a mod somewhere and has no unread modmail
393 # - None: (the default) the user is not a mod anywhere
394 return self.modmsgtime is not None
395
f596492 @MelissaCole Disallow muting of system users
MelissaCole authored Oct 6, 2015
396 def is_mutable(self, subreddit):
397 # Don't allow muting of other mods in the subreddit
398 if subreddit.is_moderator(self):
399 return False
400
401 # Don't allow muting of u/reddit or u/AutoModerator
402 return not (self == self.system_user() or
403 self == self.automoderator_user())
404
0ae8f2f @ketralnis 21 Jul 2010 merge
ketralnis authored Jul 22, 2010
405 # Used on the goldmember version of /prefs/friends
406 @memoize('account.friend_rels')
407 def friend_rels_cache(self):
4c6740c @bsimpson63 Account.friend_rels_cache: Load data attributes in query
bsimpson63 authored Jan 27, 2016
408 q = Friend._query(
409 Friend.c._thing1_id == self._id,
410 Friend.c._name == 'friend',
411 thing_data=True,
412 )
0ae8f2f @ketralnis 21 Jul 2010 merge
ketralnis authored Jul 22, 2010
413 return list(f._id for f in q)
414
415 def friend_rels(self, _update = False):
416 rel_ids = self.friend_rels_cache(_update=_update)
37e2ba9 @ketralnis * Combine cassandra clusters into a single one
ketralnis authored Oct 18, 2010
417 try:
418 rels = Friend._byID_rel(rel_ids, return_dict=False,
419 eager_load = True, data = True,
420 thing_data = True)
421 rels = list(rels)
422 except NotFound:
423 if _update:
424 raise
425 else:
426 log_text("friend-rels-bandaid 1",
427 "Had to recalc friend_rels (1) for %s" % self.name,
428 "warning")
429 return self.friend_rels(_update=True)
430
431 if not _update:
432 sorted_1 = sorted([r._thing2_id for r in rels])
433 sorted_2 = sorted(list(self.friends))
434 if sorted_1 != sorted_2:
435 g.log.error("FR1: %r" % sorted_1)
436 g.log.error("FR2: %r" % sorted_2)
437 log_text("friend-rels-bandaid 2",
438 "Had to recalc friend_rels (2) for %s" % self.name,
439 "warning")
440 self.friend_ids(_update=True)
441 return self.friend_rels(_update=True)
0ae8f2f @ketralnis 21 Jul 2010 merge
ketralnis authored Jul 22, 2010
442 return dict((r._thing2_id, r) for r in rels)
443
444 def add_friend_note(self, friend, note):
445 rels = self.friend_rels()
446 rel = rels[friend._id]
447 rel.note = note
448 rel._commit()
449
e7adea1 @Deimos /r/friends: use friends that posted most recently
Deimos authored Sep 22, 2015
450 def _get_friend_ids_by(self, data_value_name, limit):
451 friend_ids = self.friend_ids()
452 if len(friend_ids) <= limit:
453 return friend_ids
454
455 with g.stats.get_timer("friends_query.%s" % data_value_name):
456 result = self.sort_ids_by_data_value(
457 friend_ids, data_value_name, limit=limit, desc=True)
458
459 return result.fetchall()
460
461 @memoize("get_recently_submitted_friend_ids", time=10*60)
462 def get_recently_submitted_friend_ids(self, limit=100):
463 return self._get_friend_ids_by("last_submit_time", limit)
464
465 @memoize("get_recently_commented_friend_ids", time=10*60)
466 def get_recently_commented_friend_ids(self, limit=100):
467 return self._get_friend_ids_by("last_comment_time", limit)
c155eee @Deimos Stop handling 304s for comments pages/userpages
Deimos authored Sep 14, 2015
468
ea1fb6f @chromakode Redesign the account deletion page.
chromakode authored Nov 4, 2011
469 def delete(self, delete_message=None):
470 self.delete_message = delete_message
f1db8ef API: Record account deletion time
Roger Ostrander authored Apr 26, 2013
471 self.delete_time = datetime.now(g.tz)
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
472 self._deleted = True
473 self._commit()
e35b519 @spez * removed references to clear_memo
spez authored Mar 4, 2009
474
475 #update caches
476 Account._by_name(self.name, allow_deleted = True, _update = True)
477 #we need to catch an exception here since it will have been
478 #recently deleted
479 try:
480 Account._by_name(self.name, _update = True)
481 except NotFound:
482 pass
0bceab8 @JordanMilne Add queue to scrub deleted accounts of potential leaks
JordanMilne authored Jan 16, 2015
483
03dec9e @Deimos Account deletion: clean up a lot more after delay
Deimos authored Jul 26, 2015
484 # Mark this account for immediate cleanup tasks
0bceab8 @JordanMilne Add queue to scrub deleted accounts of potential leaks
JordanMilne authored Jan 16, 2015
485 amqp.add_item('account_deleted', self._fullname)
486
03dec9e @Deimos Account deletion: clean up a lot more after delay
Deimos authored Jul 26, 2015
487 # schedule further cleanup after a possible recovery period
d2ccc40 @spladug Automatically delete password hashes of deleted accounts.
spladug authored Feb 10, 2014
488 TryLater.schedule("account_deletion", self._id36,
489 delay=timedelta(days=90))
490
65b1467 Backend support for bans.
Roger Ostrander authored Oct 16, 2012
491 # 'State' bitfield properties
492 @property
493 def _banned(self):
494 return self.state & 1
495
496 @_banned.setter
497 def _banned(self, value):
498 if value and not self._banned:
499 self.state |= 1
500 # Invalidate all cookies by changing the password
501 # First back up the password so we can reverse this
502 self.backup_password = self.password
503 # New PW doesn't matter, they can't log in with it anyway.
504 # Even if their PW /was/ 'banned' for some reason, this
505 # will change the salt and thus invalidate the cookies
506 change_password(self, 'banned')
507
508 # deauthorize all access tokens
509 from r2.models.token import OAuth2AccessToken
510 from r2.models.token import OAuth2RefreshToken
511
512 OAuth2AccessToken.revoke_all_by_user(self)
513 OAuth2RefreshToken.revoke_all_by_user(self)
514 elif not value and self._banned:
515 self.state &= ~1
516
517 # Undo the password thing so they can log in
518 self.password = self.backup_password
519
520 # They're on their own for OAuth tokens, though.
521
522 self._commit()
523
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
524 @property
525 def subreddits(self):
526 from subreddit import Subreddit
527 return Subreddit.user_subreddits(self)
528
3735033 @chromakode Add support for special user distingushes.
chromakode authored Sep 20, 2011
529 def special_distinguish(self):
530 if self._t.get("special_distinguish_name"):
531 return dict((k, self._t.get("special_distinguish_"+k, None))
532 for k in ("name", "kind", "symbol", "cssclass", "label", "link"))
533 else:
534 return None
535
003ca6e @bsimpson63 Add AccountsByCanonicalEmail.
bsimpson63 authored Jan 8, 2015
536 def set_email(self, email):
537 old_email = self.email
538 self.email = email
539 self._commit()
540 AccountsByCanonicalEmail.update_email(self, old_email, email)
541
d251ba7 @KeyserSosa * Improvements to the email verification system
KeyserSosa authored May 20, 2010
542 def canonical_email(self):
76594cd @spladug Refactor and reorganize email canonicalization; add some tests.
spladug authored Dec 22, 2012
543 return canonicalize_email(self.email)
d251ba7 @KeyserSosa * Improvements to the email verification system
KeyserSosa authored May 20, 2010
544
2869eaf @ketralnis New features:
ketralnis authored May 3, 2010
545 @classmethod
e87f520 @KeyserSosa New Features:
KeyserSosa authored May 4, 2010
546 def system_user(cls):
547 try:
548 return cls._by_name(g.system_user)
52da322 @KeyserSosa Bugfixes:
KeyserSosa authored Jun 25, 2010
549 except (NotFound, AttributeError):
e87f520 @KeyserSosa New Features:
KeyserSosa authored May 4, 2010
550 return None
a402d48 New features:
Mike authored May 4, 2010
551
f596492 @MelissaCole Disallow muting of system users
MelissaCole authored Oct 6, 2015
552 @classmethod
553 def automoderator_user(cls):
554 try:
555 return cls._by_name(g.automoderator_account)
556 except (NotFound, AttributeError):
557 return None
558
7f1a728 @MelissaCole Gold: Add ability to override individual sr styles
MelissaCole authored Apr 8, 2015
559 def use_subreddit_style(self, sr):
560 """Return whether to show subreddit stylesheet depending on
561 individual selection if available, else use pref_show_stylesheets"""
562 # if FakeSubreddit, there is no stylesheet
563 if not hasattr(sr, '_id'):
564 return False
565 if not feature.is_enabled('stylesheets_everywhere'):
566 return self.pref_show_stylesheets
567 # if stylesheet isn't individually enabled/disabled, use global pref
568 return bool(getattr(self, "sr_style_%s_enabled" % sr._id,
569 self.pref_show_stylesheets))
570
571 def set_subreddit_style(self, sr, use_style):
572 if hasattr(sr, '_id'):
573 setattr(self, "sr_style_%s_enabled" % sr._id, use_style)
574 self._commit()
575
4631751 User option to block flair, and layout fixes.
Logan Hanks authored Jul 6, 2011
576 def flair_enabled_in_sr(self, sr_id):
30c9134 @chromakode Tweak flair preference lookup to not break on multi ids.
chromakode authored Mar 9, 2013
577 return getattr(self, 'flair_%s_enabled' % sr_id, True)
4631751 User option to block flair, and layout fixes.
Logan Hanks authored Jul 6, 2011
578
bedf7e9 @Deimos Account flair methods: allow obeying pref
Deimos authored May 12, 2015
579 def flair_text(self, sr_id, obey_disabled=False):
580 if obey_disabled and not self.flair_enabled_in_sr(sr_id):
581 return None
d6f2574 @bsimpson63 Cleanup wrapped_flair.
bsimpson63 authored Jun 20, 2014
582 return getattr(self, 'flair_%s_text' % sr_id, None)
583
bedf7e9 @Deimos Account flair methods: allow obeying pref
Deimos authored May 12, 2015
584 def flair_css_class(self, sr_id, obey_disabled=False):
585 if obey_disabled and not self.flair_enabled_in_sr(sr_id):
586 return None
d6f2574 @bsimpson63 Cleanup wrapped_flair.
bsimpson63 authored Jun 20, 2014
587 return getattr(self, 'flair_%s_css_class' % sr_id, None)
588
b24ba98 @Deimos Refactor flair API endpoints
Deimos authored Mar 2, 2015
589 def can_flair_in_sr(self, user, sr):
590 """Return whether a user can set this one's flair in a subreddit."""
591 can_assign_own = self._id == user._id and sr.flair_self_assign_enabled
592
593 return can_assign_own or sr.is_moderator_with_perms(user, "flair")
594
595 def set_flair(self, subreddit, text=None, css_class=None, set_by=None,
596 log_details="edit"):
597 log_details = "flair_%s" % log_details
598 if not text and not css_class:
599 # set to None instead of potentially empty strings
600 text = css_class = None
601 subreddit.remove_flair(self)
602 log_details = "flair_delete"
603 elif not subreddit.is_flair(self):
604 subreddit.add_flair(self)
605
606 setattr(self, 'flair_%s_text' % subreddit._id, text)
607 setattr(self, 'flair_%s_css_class' % subreddit._id, css_class)
608 self._commit()
609
610 if set_by and set_by != self:
611 ModAction.create(subreddit, set_by, action='editflair',
612 target=self, details=log_details)
613
fc9abd1 @kemitche Award-claiming via one-time links.
kemitche authored Mar 8, 2013
614 def get_trophy_id(self, uid):
615 '''Return the ID of the Trophy associated with the given "uid"
616
617 `uid` - The unique identifier for the Trophy to look up
618
619 '''
620 return getattr(self, 'received_trophy_%s' % uid, None)
621
622 def set_trophy_id(self, uid, trophy_id):
623 '''Recored that a user has received a Trophy with "uid"
624
625 `uid` - The trophy "type" that the user should only have one of
626 `trophy_id` - The ID of the corresponding Trophy object
627
628 '''
629 return setattr(self, 'received_trophy_%s' % uid, trophy_id)
630
75da617 @rram account: Add employee property.
rram authored Aug 8, 2013
631 @property
632 def employee(self):
633 """Return if the user is an employee.
634
635 Being an employee grants them various special privileges.
636
637 """
dd925f4 @rram account: Protect employees against logged out users.
rram authored Aug 9, 2013
638 return (hasattr(self, 'name') and
639 (self.name in g.admins or
640 self.name in g.sponsors or
641 self.name in g.employees))
75da617 @rram account: Add employee property.
rram authored Aug 8, 2013
642
e394579 @bsimpson63 Per user CPM overrides.
bsimpson63 authored Sep 18, 2013
643 @property
19d9968 @bsimpson63 Don't PM users with gold subscription about expiration.
bsimpson63 authored Oct 24, 2013
644 def has_gold_subscription(self):
210dd1b @bsimpson63 Store stripe customer id in Account.gold_subscr_id.
bsimpson63 authored Nov 16, 2013
645 return bool(getattr(self, 'gold_subscr_id', None))
646
647 @property
648 def has_paypal_subscription(self):
649 return (self.has_gold_subscription and
650 not self.gold_subscr_id.startswith('cus_'))
651
652 @property
653 def has_stripe_subscription(self):
654 return (self.has_gold_subscription and
655 self.gold_subscr_id.startswith('cus_'))
19d9968 @bsimpson63 Don't PM users with gold subscription about expiration.
bsimpson63 authored Oct 24, 2013
656
82b870d @Deimos Gold: add preference to auto-renew with a creddit
Deimos authored Aug 4, 2014
657 @property
658 def gold_will_autorenew(self):
659 return (self.has_gold_subscription or
660 (self.pref_creddit_autorenew and self.gold_creddits > 0))
661
4e03b97 @madbook Timeouts: Move logic to get days left in timeout to Account.
madbook authored Sep 9, 2015
662 @property
0407f4f @xiongchiamiov Refactor ban models
xiongchiamiov authored Sep 11, 2015
663 def timeout_expiration(self):
664 """Find the expiration date of the user's temp-timeout as a datetime
665 object.
666
667 Returns None if no temp-timeout found.
668 """
669 if not self.in_timeout:
670 return None
671
672 return TempTimeout.search(self.name).get(self.name)
673
674 @property
4e03b97 @madbook Timeouts: Move logic to get days left in timeout to Account.
madbook authored Sep 9, 2015
675 def days_remaining_in_timeout(self):
676 if not self.in_timeout:
677 return 0
678
0407f4f @xiongchiamiov Refactor ban models
xiongchiamiov authored Sep 11, 2015
679 expires = self.timeout_expiration
4e03b97 @madbook Timeouts: Move logic to get days left in timeout to Account.
madbook authored Sep 9, 2015
680
681 if not expires:
682 return 0
683
7bea9fd @MelissaCole Add buffer of 1 hour when calculating timeout expiration
MelissaCole authored Jan 6, 2016
684 # TryLater runs periodically, so if the suspension expires
685 # within that time, then the remaining number of days is 0
817c1c6 @MelissaCole Timeouts: Fix the timeout days remaining buffer
MelissaCole authored Jan 13, 2016
686 # which is the same as a permanent suspension. Return 1 day
687 # remaining if it already expired but the TryLater queue hasn't
688 # cleared it yet.
689 days_left = (expires - datetime.now(g.tz)).days + 1
690 return max(days_left, 1)
4e03b97 @madbook Timeouts: Move logic to get days left in timeout to Account.
madbook authored Sep 9, 2015
691
35e3997 @weffey Takedown tool
weffey authored Apr 15, 2015
692 def incr_admin_takedown_strikes(self, amt=1):
693 return self._incr('admin_takedown_strikes', amt)
694
e394579 @bsimpson63 Per user CPM overrides.
bsimpson63 authored Sep 18, 2013
695
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
696 class FakeAccount(Account):
697 _nodb = True
5ef76b9 @KeyserSosa New features:
KeyserSosa authored May 3, 2010
698 pref_no_profanity = True
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
699
b06dc9b @chromakode Add equality implementation for Account model.
chromakode authored Sep 14, 2012
700 def __eq__(self, other):
701 return self is other
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
702
33b15bc @spladug Split the admin cookie out from the session cookie.
spladug authored Mar 12, 2012
703 def valid_admin_cookie(cookie):
704 if g.read_only_mode:
a42505c @spladug Keep admin cookie around if actively used.
spladug authored Mar 14, 2012
705 return (False, None)
33b15bc @spladug Split the admin cookie out from the session cookie.
spladug authored Mar 12, 2012
706
707 # parse the cookie
708 try:
a42505c @spladug Keep admin cookie around if actively used.
spladug authored Mar 14, 2012
709 first_login, last_request, hash = cookie.split(',')
33b15bc @spladug Split the admin cookie out from the session cookie.
spladug authored Mar 12, 2012
710 except ValueError:
a42505c @spladug Keep admin cookie around if actively used.
spladug authored Mar 14, 2012
711 return (False, None)
33b15bc @spladug Split the admin cookie out from the session cookie.
spladug authored Mar 12, 2012
712
713 # make sure it's a recent cookie
714 try:
a42505c @spladug Keep admin cookie around if actively used.
spladug authored Mar 14, 2012
715 first_login_time = datetime.strptime(first_login, COOKIE_TIMESTAMP_FORMAT)
716 last_request_time = datetime.strptime(last_request, COOKIE_TIMESTAMP_FORMAT)
33b15bc @spladug Split the admin cookie out from the session cookie.
spladug authored Mar 12, 2012
717 except ValueError:
a42505c @spladug Keep admin cookie around if actively used.
spladug authored Mar 14, 2012
718 return (False, None)
33b15bc @spladug Split the admin cookie out from the session cookie.
spladug authored Mar 12, 2012
719
a42505c @spladug Keep admin cookie around if actively used.
spladug authored Mar 14, 2012
720 cookie_age = datetime.utcnow() - first_login_time
33b15bc @spladug Split the admin cookie out from the session cookie.
spladug authored Mar 12, 2012
721 if cookie_age.total_seconds() > g.ADMIN_COOKIE_TTL:
a42505c @spladug Keep admin cookie around if actively used.
spladug authored Mar 14, 2012
722 return (False, None)
723
724 idle_time = datetime.utcnow() - last_request_time
725 if idle_time.total_seconds() > g.ADMIN_COOKIE_MAX_IDLE:
726 return (False, None)
33b15bc @spladug Split the admin cookie out from the session cookie.
spladug authored Mar 12, 2012
727
728 # validate
a42505c @spladug Keep admin cookie around if actively used.
spladug authored Mar 14, 2012
729 expected_cookie = c.user.make_admin_cookie(first_login, last_request)
730 return (constant_time_compare(cookie, expected_cookie),
731 first_login)
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
732
733
8dfd73b @spladug Add framework for RFC-6238: Time-Based One Time Password Algorithm.
spladug authored Jul 22, 2012
734 def valid_otp_cookie(cookie):
735 if g.read_only_mode:
736 return False
737
738 # parse the cookie
739 try:
740 remembered_at, signature = cookie.split(",")
741 except ValueError:
742 return False
743
744 # make sure it hasn't expired
745 try:
746 remembered_at_time = datetime.strptime(remembered_at, COOKIE_TIMESTAMP_FORMAT)
747 except ValueError:
748 return False
749
750 age = datetime.utcnow() - remembered_at_time
751 if age.total_seconds() > g.OTP_COOKIE_TTL:
752 return False
753
754 # validate
755 expected_cookie = c.user.make_otp_cookie(remembered_at)
756 return constant_time_compare(cookie, expected_cookie)
757
758
a402d48 New features:
Mike authored May 4, 2010
759 def valid_feed(name, feedhash, path):
760 if name and feedhash and path:
761 from r2.lib.template_helpers import add_sr
762 path = add_sr(path)
763 try:
764 user = Account._by_name(name)
765 if (user.pref_private_feeds and
83058d4 @ekarulf Use constant-time string comparison for auth.
ekarulf authored Jul 11, 2011
766 constant_time_compare(feedhash, make_feedhash(user, path))):
a402d48 New features:
Mike authored May 4, 2010
767 return user
768 except NotFound:
769 pass
770
771 def make_feedhash(user, path):
3366083 @spladug Create a vault for secret tokens and move some into it.
spladug authored Nov 15, 2013
772 return hashlib.sha1("".join([user.name, user.password,
773 g.secrets["FEEDSECRET"]])
a402d48 New features:
Mike authored May 4, 2010
774 ).hexdigest()
775
776 def make_feedurl(user, path, ext = "rss"):
777 u = UrlParser(path)
778 u.update_query(user = user.name,
779 feed = make_feedhash(user, path))
780 u.set_extension(ext)
781 return u.unparse()
782
722fbf2 Passwords: Enable checks against other passwords
Roger Ostrander authored Aug 5, 2014
783 def valid_password(a, password, compare_password=None):
a311805 @spladug Switch to bcrypt for password hashing.
spladug authored Oct 20, 2011
784 # bail out early if the account or password's invalid
785 if not hasattr(a, 'name') or not hasattr(a, 'password') or not password:
786 return False
787
722fbf2 Passwords: Enable checks against other passwords
Roger Ostrander authored Aug 5, 2014
788 convert_password = False
789 if compare_password is None:
790 convert_password = True
791 compare_password = a.password
792
a311805 @spladug Switch to bcrypt for password hashing.
spladug authored Oct 20, 2011
793 # standardize on utf-8 encoding
794 password = filters._force_utf8(password)
795
722fbf2 Passwords: Enable checks against other passwords
Roger Ostrander authored Aug 5, 2014
796 if compare_password.startswith('$2a$'):
a44f6f4 @spladug Upgrade passwords on log in when bcrypt work factor changed.
spladug authored Aug 12, 2012
797 # it's bcrypt.
722fbf2 Passwords: Enable checks against other passwords
Roger Ostrander authored Aug 5, 2014
798 expected_hash = bcrypt.hashpw(password, compare_password)
799 if not constant_time_compare(compare_password, expected_hash):
a44f6f4 @spladug Upgrade passwords on log in when bcrypt work factor changed.
spladug authored Aug 13, 2012
800 return False
a311805 @spladug Switch to bcrypt for password hashing.
spladug authored Oct 20, 2011
801
a44f6f4 @spladug Upgrade passwords on log in when bcrypt work factor changed.
spladug authored Aug 13, 2012
802 # if it's using the current work factor, we're done, but if it's not
803 # we'll have to rehash.
804 # the format is $2a$workfactor$salt+hash
722fbf2 Passwords: Enable checks against other passwords
Roger Ostrander authored Aug 5, 2014
805 work_factor = int(compare_password.split("$")[2])
a44f6f4 @spladug Upgrade passwords on log in when bcrypt work factor changed.
spladug authored Aug 13, 2012
806 if work_factor == g.bcrypt_work_factor:
807 return a
808 else:
809 # alright, so it's not bcrypt. how old is it?
810 # if the length of the stored hash is 43 bytes, the sha-1 hash has a salt
811 # otherwise it's sha-1 with no salt.
812 salt = ''
722fbf2 Passwords: Enable checks against other passwords
Roger Ostrander authored Aug 5, 2014
813 if len(compare_password) == 43:
814 salt = compare_password[:3]
a44f6f4 @spladug Upgrade passwords on log in when bcrypt work factor changed.
spladug authored Aug 13, 2012
815 expected_hash = passhash(a.name, password, salt)
816
722fbf2 Passwords: Enable checks against other passwords
Roger Ostrander authored Aug 5, 2014
817 if not constant_time_compare(compare_password, expected_hash):
a44f6f4 @spladug Upgrade passwords on log in when bcrypt work factor changed.
spladug authored Aug 13, 2012
818 return False
a311805 @spladug Switch to bcrypt for password hashing.
spladug authored Oct 20, 2011
819
820 # since we got this far, it's a valid password but in an old format
821 # let's upgrade it
722fbf2 Passwords: Enable checks against other passwords
Roger Ostrander authored Aug 5, 2014
822 if convert_password:
823 a.password = bcrypt_password(password)
824 a._commit()
a311805 @spladug Switch to bcrypt for password hashing.
spladug authored Oct 20, 2011
825 return a
826
827 def bcrypt_password(password):
828 salt = bcrypt.gensalt(log_rounds=g.bcrypt_work_factor)
829 return bcrypt.hashpw(password, salt)
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
830
831 def passhash(username, password, salt = ''):
832 if salt is True:
833 salt = randstr(3)
834 tohash = '%s%s %s' % (salt, username, password)
c9c65dd @spladug Replace references to deprecated sha module with hashlib.
spladug authored Jun 16, 2012
835 return salt + hashlib.sha1(tohash).hexdigest()
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
836
c852953 fixed recover password
shuffman authored Jun 26, 2008
837 def change_password(user, newpassword):
a311805 @spladug Switch to bcrypt for password hashing.
spladug authored Oct 20, 2011
838 user.password = bcrypt_password(newpassword)
c852953 fixed recover password
shuffman authored Jun 26, 2008
839 user._commit()
abf6f6e @alienth Store a LastModified update on account password change.
alienth authored May 19, 2014
840 LastModified.touch(user._fullname, 'Password')
c852953 fixed recover password
shuffman authored Jun 26, 2008
841 return True
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
842
37cac44 @bsimpson63 account.register: Use lock to prevent creating Accounts with same name
bsimpson63 authored Apr 25, 2016
843
b55934a @spladug Account: ensure all non-defaulted attributes are atomically created.
spladug authored Oct 25, 2012
844 def register(name, password, registration_ip):
37cac44 @bsimpson63 account.register: Use lock to prevent creating Accounts with same name
bsimpson63 authored Apr 25, 2016
845 # get a lock for registering an Account with this name to prevent
846 # simultaneous operations from creating multiple Accounts with the same name
847 with g.make_lock("account_register", "register_%s" % name.lower()):
848 try:
849 account = Account._by_name(name)
850 raise AccountExists
851 except NotFound:
852 account = Account(
853 name=name,
854 password=bcrypt_password(password),
855 # new accounts keep the profanity filter settings until opting out
856 pref_no_profanity=True,
857 registration_ip=registration_ip,
858 )
859 account._commit()
860
861 # update Account._by_name to pick up this new name->Account
862 Account._by_name(name, _update=True)
863 Account._by_name(name, allow_deleted=True, _update=True)
864
865 return account
e35b519 @spez * removed references to clear_memo
spez authored Mar 4, 2009
866
4778b17 @KeyserSosa initial checkin
KeyserSosa authored Jun 18, 2008
867
868 class Friend(Relation(Account, Account)): pass
bf9f43c @KeyserSosa Messaging/commenting
KeyserSosa authored Dec 1, 2009
869
4d7a2fa @kemitche Allow users to block users that harass them
kemitche authored Jun 24, 2011
870 Account.__bases__ += (UserRel('friend', Friend, disable_reverse_ids_fn=True),
6c10091 @kemitche Reverse lookup of blocked user
kemitche authored Jul 12, 2011
871 UserRel('enemy', Friend, disable_reverse_ids_fn=False))
4440ccf @KeyserSosa overhaul of JS and form handling code, not based on jQuery
KeyserSosa authored Jan 26, 2009
872
873 class DeletedUser(FakeAccount):
874 @property
875 def name(self):
876 return '[deleted]'
877
a402d48 New features:
Mike authored May 4, 2010
878 @property
879 def _deleted(self):
880 return True
881
4440ccf @KeyserSosa overhaul of JS and form handling code, not based on jQuery
KeyserSosa authored Jan 26, 2009
882 def _fullname(self):
883 raise NotImplementedError
884
885 def _id(self):
886 raise NotImplementedError
a402d48 New features:
Mike authored May 4, 2010
887
888 def __setattr__(self, attr, val):
889 if attr == '_deleted':
890 pass
891 else:
892 object.__setattr__(self, attr, val)
2fe83ed @alienth Add AccountActivityBySR.
alienth authored Aug 13, 2012
893
d2ccc40 @spladug Automatically delete password hashes of deleted accounts.
spladug authored Feb 10, 2014
894
ff5e5e0 @bsimpson63 Let users block messages from subreddits.
bsimpson63 authored Sep 29, 2014
895 class BlockedSubredditsByAccount(tdb_cassandra.DenormalizedRelation):
896 _use_db = True
897 _last_modified_name = 'block_subreddit'
898 _read_consistency_level = tdb_cassandra.CL.QUORUM
899 _write_consistency_level = tdb_cassandra.CL.QUORUM
900 _connection_pool = 'main'
901 _views = []
902
903 @classmethod
904 def value_for(cls, thing1, thing2):
905 return ''
906
907 @classmethod
908 def block(cls, user, sr):
909 cls.create(user, sr)
910
911 @classmethod
912 def unblock(cls, user, sr):
913 cls.destroy(user, sr)
914
915 @classmethod
916 def is_blocked(cls, user, sr):
917 try:
df77799 @bsimpson63 BlockedSubredditsByAccount: fix is_blocked method.
bsimpson63 authored Sep 30, 2014
918 r = cls.fast_query(user, [sr])
ff5e5e0 @bsimpson63 Let users block messages from subreddits.
bsimpson63 authored Sep 29, 2014
919 except tdb_cassandra.NotFound:
920 return False
df77799 @bsimpson63 BlockedSubredditsByAccount: fix is_blocked method.
bsimpson63 authored Sep 30, 2014
921 return (user, sr) in r
ff5e5e0 @bsimpson63 Let users block messages from subreddits.
bsimpson63 authored Sep 29, 2014
922
923
d2ccc40 @spladug Automatically delete password hashes of deleted accounts.
spladug authored Feb 10, 2014
924 @trylater_hooks.on("trylater.account_deletion")
03dec9e @Deimos Account deletion: clean up a lot more after delay
Deimos authored Jul 26, 2015
925 def deleted_account_cleanup(data):
926 from r2.models import Subreddit
927 from r2.models.admin_notes import AdminNotesBySystem
928 from r2.models.flair import Flair
929 from r2.models.token import OAuth2Client
930
fb507e5 @xiongchiamiov TryLater: use more generic parameter name
xiongchiamiov authored Dec 5, 2014
931 for account_id36 in data.itervalues():
d2ccc40 @spladug Automatically delete password hashes of deleted accounts.
spladug authored Feb 10, 2014
932 account = Account._byID36(account_id36, data=True)
933
934 if not account._deleted:
935 continue
936
03dec9e @Deimos Account deletion: clean up a lot more after delay
Deimos authored Jul 26, 2015
937 # wipe the account's password and email address
d2ccc40 @spladug Automatically delete password hashes of deleted accounts.
spladug authored Feb 10, 2014
938 account.password = ""
03dec9e @Deimos Account deletion: clean up a lot more after delay
Deimos authored Jul 26, 2015
939 account.email = ""
940 account.email_verified = False
941
942 notes = ""
943
944 # "noisy" rel removals, we'll record all of these in the account's
945 # usernotes in case we need the information later
946 rel_removal_descriptions = {
947 "moderator": "Unmodded",
948 "moderator_invite": "Cancelled mod invite",
949 "contributor": "Removed as contributor",
950 "banned": "Unbanned",
951 "wikibanned": "Un-wikibanned",
952 "wikicontributor": "Removed as wiki contributor",
953 }
954 if account.has_subscribed:
955 rel_removal_descriptions["subscriber"] = "Unsubscribed"
956
957 for rel_type, description in rel_removal_descriptions.iteritems():
958 try:
959 ids_fn = getattr(Subreddit, "reverse_%s_ids" % rel_type)
960 sr_ids = ids_fn(account)
961
962 sr_names = []
963 srs = Subreddit._byID(sr_ids, data=True, return_dict=False)
964 for subreddit in srs:
965 remove_fn = getattr(subreddit, "remove_" + rel_type)
966 remove_fn(account)
967 sr_names.append(subreddit.name)
968
969 if description and sr_names:
970 sr_list = ", ".join(sr_names)
971 notes += "* %s from %s\n" % (description, sr_list)
972 except Exception as e:
973 notes += "* Error cleaning up %s rels: %s\n" % (rel_type, e)
974
975 # silent rel removals, no record left in the usernotes
976 rel_classes = {
977 "flair": Flair,
978 "friend": Friend,
979 "enemy": Friend,
980 }
981
982 for rel_name, rel_cls in rel_classes.iteritems():
983 try:
984 rels = rel_cls._query(
985 rel_cls.c._thing2_id == account._id,
986 rel_cls.c._name == rel_name,
987 eager_load=True,
988 )
989 for rel in rels:
990 remove_fn = getattr(rel._thing1, "remove_" + rel_name)
991 remove_fn(account)
992 except Exception as e:
993 notes += "* Error cleaning up %s rels: %s\n" % (rel_name, e)
994
995 # add the note with info about the major changes to the account
996 if notes:
997 AdminNotesBySystem.add(
998 system_name="user",
999 subject=account.name,
1000 note="Account deletion cleanup summary:\n\n%s" % notes,
1001 author="<automated>",
1002 when=datetime.now(g.tz),
1003 )
1004
d2ccc40 @spladug Automatically delete password hashes of deleted accounts.
spladug authored Feb 10, 2014
1005 account._commit()
003ca6e @bsimpson63 Add AccountsByCanonicalEmail.
bsimpson63 authored Jan 9, 2015
1006
1007
1008 class AccountsByCanonicalEmail(tdb_cassandra.View):
1009 __metaclass__ = tdb_cassandra.ThingMeta
1010
1011 _use_db = True
1012 _compare_with = tdb_cassandra.UTF8_TYPE
1013 _extra_schema_creation_args = dict(
1014 key_validation_class=tdb_cassandra.UTF8_TYPE,
1015 )
1016
1017 @classmethod
1018 def update_email(cls, account, old, new):
1019 old, new = map(canonicalize_email, (old, new))
1020
1021 if old == new:
1022 return
1023
1024 with cls._cf.batch() as b:
1025 if old:
1026 b.remove(old, {account._id36: ""})
1027 if new:
1028 b.insert(new, {account._id36: ""})
1029
1030 @classmethod
1031 def get_accounts(cls, email_address):
1032 canonical = canonicalize_email(email_address)
1033 if not canonical:
1034 return []
1035 account_id36s = cls.get_time_sorted_columns(canonical).keys()
1036 return Account._byID36(account_id36s, data=True, return_dict=False)
495d761 @Deimos Track subreddit participation directly
Deimos authored May 15, 2015
1037
1038
1039 class SubredditParticipationByAccount(tdb_cassandra.DenormalizedRelation):
1040 _use_db = True
1041 _write_last_modified = False
1042 _views = []
1043 _extra_schema_creation_args = {
1044 "key_validation_class": tdb_cassandra.ASCII_TYPE,
1045 "default_validation_class": tdb_cassandra.DATE_TYPE,
1046 }
1047
1048 @classmethod
1049 def value_for(cls, thing1, thing2):
1050 return datetime.now(g.tz)
1051
1052 @classmethod
1053 def mark_participated(cls, account, subreddit):
1054 cls.create(account, [subreddit])
ae1296c @MelissaCole Create quarantined subreddits content gate
MelissaCole authored Jul 21, 2015
1055
1056
1057 class QuarantinedSubredditOptInsByAccount(tdb_cassandra.DenormalizedRelation):
1058 _use_db = True
1059 _last_modified_name = 'QuarantineSubredditOptin'
1060 _read_consistency_level = tdb_cassandra.CL.QUORUM
1061 _write_consistency_level = tdb_cassandra.CL.QUORUM
6714b63 @MelissaCole Quarantined optins: Include schema args for cassandra
MelissaCole authored Jul 24, 2015
1062 _extra_schema_creation_args = {
1063 "key_validation_class": tdb_cassandra.ASCII_TYPE,
1064 "default_validation_class": tdb_cassandra.DATE_TYPE,
1065 }
ae1296c @MelissaCole Create quarantined subreddits content gate
MelissaCole authored Jul 21, 2015
1066 _connection_pool = 'main'
1067 _views = []
1068
1069 @classmethod
1070 def value_for(cls, thing1, thing2):
1071 return datetime.now(g.tz)
1072
1073 @classmethod
1074 def opt_in(cls, account, subreddit):
1075 if subreddit.quarantine:
1076 cls.create(account, subreddit)
1077
1078 @classmethod
1079 def opt_out(cls, account, subreddit):
8b9df0f @MelissaCole Unsubscribe from quarantined subreddit on opt out
MelissaCole authored Aug 4, 2015
1080 if subreddit.is_subscriber(account):
1081 subreddit.remove_subscriber(account)
ae1296c @MelissaCole Create quarantined subreddits content gate
MelissaCole authored Jul 21, 2015
1082 cls.destroy(account, subreddit)
1083
1084 @classmethod
1085 def is_opted_in(cls, user, subreddit):
1086 try:
1087 r = cls.fast_query(user, [subreddit])
1088 except tdb_cassandra.NotFound:
1089 return False
1090 return (user, subreddit) in r
Something went wrong with that request. Please try again.