This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

Split the admin cookie out from the session cookie.

  • Loading branch information...
1 parent e186b4c commit 33b15bc265fa6991324a3b23df341223ee8d46ae @spladug spladug committed Mar 12, 2012
View
@@ -91,6 +91,8 @@ shutdown_secret = 12345
https_endpoint =
# name of the cookie to drop with login information
login_cookie = reddit_session
+# name of the admin cookie
+admin_cookie = reddit_admin
# the work factor for bcrypt, increment this every time computers double in
# speed. don't worry, changing this won't break old passwords
bcrypt_work_factor = 12
@@ -385,6 +387,8 @@ REPLY_AGE_LIMIT = 180
VOTE_AGE_LIMIT = 180
# minimum age, in days, of an account to be eligible to create a community
min_membership_create_community = 30
+# how long the admin cookie should be valid for (in seconds)
+ADMIN_COOKIE_TTL = 1800
# min amount of karma to edit
WIKI_KARMA = 100
View
@@ -2462,5 +2462,5 @@ def POST_adminon(self, form, jquery, dest):
if form.has_errors('password', errors.WRONG_PASSWORD):
return
- self.login(c.user, admin = True, rem = True)
+ self.enable_admin_mode(c.user)
form.redirect(dest)
@@ -36,10 +36,9 @@ def pre(self):
MinimalController.pre(self)
# override user loggedin behavior to ensure this page always
# uses the page cache
- (user, maybe_admin) = \
- valid_cookie(c.cookies[g.login_cookie].value
- if g.login_cookie in c.cookies
- else '')
+ user = valid_cookie(c.cookies[g.login_cookie].value
+ if g.login_cookie in c.cookies
+ else '')
if user:
self.user_is_loggedin = True
@@ -1076,7 +1076,7 @@ def GET_adminoff(self, dest):
"""disable admin interaction with site."""
if not c.user.name in g.admins:
return self.abort404()
- self.login(c.user, admin = False, rem = True)
+ self.disable_admin_mode(c.user)
return self.redirect(dest)
def GET_validuser(self):
@@ -29,7 +29,7 @@
from r2.lib.utils import http_utils, is_subdomain, UniqueIterator, ip_and_slash16
from r2.lib.cache import LocalCache, make_key, MemcachedError
import random as rand
-from r2.models.account import valid_cookie, FakeAccount, valid_feed
+from r2.models.account import valid_cookie, FakeAccount, valid_feed, valid_admin_cookie
from r2.models.subreddit import Subreddit
from r2.models import *
from errors import ErrorSet
@@ -40,7 +40,7 @@
from Cookie import CookieError
from copy import copy
from Cookie import CookieError
-from datetime import datetime
+from datetime import datetime, timedelta
from hashlib import sha1, md5
from urllib import quote, unquote
import simplejson
@@ -746,14 +746,25 @@ def api_wrapper(self, kw):
class RedditController(MinimalController):
@staticmethod
- def login(user, admin = False, rem = False):
- c.cookies[g.login_cookie] = Cookie(value = user.make_cookie(admin = admin),
+ def login(user, rem=False):
+ c.cookies[g.login_cookie] = Cookie(value = user.make_cookie(),
expires = NEVER if rem else None)
@staticmethod
- def logout(admin = False):
+ def logout():
c.cookies[g.login_cookie] = Cookie(value='', expires=DELETE)
+ @staticmethod
+ def enable_admin_mode(user):
+ expiration_time = datetime.utcnow() + timedelta(seconds=g.ADMIN_COOKIE_TTL)
+ expiration = expiration_time.strftime('%a, %d %b %Y %H:%M:%S GMT')
+ c.cookies[g.admin_cookie] = Cookie(value=user.make_admin_cookie(),
+ expires=expiration)
+
+ @staticmethod
+ def disable_admin_mode(user):
+ c.cookies[g.admin_cookie] = Cookie(value='', expires=DELETE)
+
def pre(self):
c.response_wrappers = []
MinimalController.pre(self)
@@ -781,12 +792,15 @@ def pre(self):
# no logins for RSS feed unless valid_feed has already been called
if not c.user:
if c.extension != "rss":
- (c.user, maybe_admin) = \
- valid_cookie(c.cookies[g.login_cookie].value
- if g.login_cookie in c.cookies
- else '')
- if c.user:
- c.user_is_loggedin = True
+ session_cookie = c.cookies.get(g.login_cookie)
+ if session_cookie:
+ c.user = valid_cookie(session_cookie.value)
+ if c.user:
+ c.user_is_loggedin = True
+
+ admin_cookie = c.cookies.get(g.admin_cookie)
+ if c.user_is_loggedin and admin_cookie:
+ maybe_admin = valid_admin_cookie(admin_cookie.value)
if not c.user:
c.user = UnloggedUser(get_browser_langs())
View
@@ -116,6 +116,7 @@ class Globals(object):
'MODWINDOW',
'RATELIMIT',
'QUOTA_THRESHOLD',
+ 'ADMIN_COOKIE_TTL',
'num_comments',
'max_comments',
'max_comments_gold',
View
@@ -753,11 +753,10 @@ def __init__(self, dest, *args, **kwargs):
*args, **kwargs)
def content(self):
- return PasswordVerificationForm("adminon", dest=self.dest)
+ return PasswordVerificationForm(dest=self.dest)
class PasswordVerificationForm(Templated):
- def __init__(self, api, dest):
- self.api = api
+ def __init__(self, dest):
self.dest = dest
Templated.__init__(self)
View
@@ -30,12 +30,15 @@
from r2.lib import filters
from r2.lib.log import log_text
-from pylons import g
+from pylons import c, g, request
from pylons.i18n import _
import time, sha
from copy import copy
from datetime import datetime, timedelta
import bcrypt
+import hmac
+import hashlib
+
class AccountExists(Exception): pass
@@ -202,16 +205,22 @@ def update_last_visit(self, current_time):
(self.name, prev_visit, current_time))
set_last_visit(self)
- def make_cookie(self, timestr = None, admin = False):
+ def make_cookie(self, timestr=None):
if not self._loaded:
self._load()
timestr = timestr or time.strftime('%Y-%m-%dT%H:%M:%S')
id_time = str(self._id) + ',' + timestr
to_hash = ','.join((id_time, self.password, g.SECRET))
- if admin:
- to_hash += 'admin'
return id_time + ',' + sha.new(to_hash).hexdigest()
+ def make_admin_cookie(self, timestr=None):
+ if not self._loaded:
+ self._load()
+ timestr = timestr or datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S')
+ hashable = ','.join((timestr, request.ip, request.user_agent, self.password))
+ mac = hmac.new(g.SECRET, hashable, hashlib.sha1).hexdigest()
+ return ','.join((timestr, mac))
+
def needs_captcha(self):
return not g.disable_captcha and self.link_karma < 1
@@ -554,23 +563,46 @@ def valid_cookie(cookie):
uid, timestr, hash = cookie.split(',')
uid = int(uid)
except:
- return (False, False)
+ return False
if g.read_only_mode:
- return (False, False)
+ return False
try:
account = Account._byID(uid, True)
if account._deleted:
- return (False, False)
+ return False
except NotFound:
- return (False, False)
+ return False
+
+ if constant_time_compare(cookie, account.make_cookie(timestr)):
+ return account
+ return False
+
+
+def valid_admin_cookie(cookie):
+ if g.read_only_mode:
+ return False
+
+ # parse the cookie
+ try:
+ timestr, hash = cookie.split(',')
+ except ValueError:
+ return False
+
+ # make sure it's a recent cookie
+ try:
+ cookie_time = datetime.strptime(timestr, '%Y-%m-%dT%H:%M:%S')
+ except ValueError:
+ return False
+
+ cookie_age = datetime.utcnow() - cookie_time
+ if cookie_age.total_seconds() > g.ADMIN_COOKIE_TTL:
+ return False
+
+ # validate
+ return constant_time_compare(cookie, c.user.make_admin_cookie(timestr))
- if constant_time_compare(cookie, account.make_cookie(timestr, admin = False)):
- return (account, False)
- elif constant_time_compare(cookie, account.make_cookie(timestr, admin = True)):
- return (account, True)
- return (False, False)
def valid_feed(name, feedhash, path):
if name and feedhash and path:

0 comments on commit 33b15bc

Please sign in to comment.