mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-26 15:28:37 -05:00
Refactor user authentication to be more modular.
This allows alternate authentication methods to be easily plugged in for custom installs of reddit, such as LDAP on intranets.
This commit is contained in:
committed by
Keith Mitchell
parent
c7f92ca960
commit
2a8cc84faa
@@ -115,6 +115,8 @@ login_cookie = reddit_session
|
||||
admin_cookie = reddit_admin
|
||||
# name of the otp cookie
|
||||
otp_cookie = reddit_otp
|
||||
# how to authenticate users. see r2/lib/authentication.py for options
|
||||
authentication_provider = cookie
|
||||
# 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
|
||||
|
||||
@@ -29,7 +29,7 @@ from r2.lib import pages, utils, filters, amqp, stats
|
||||
from r2.lib.utils import http_utils, is_subdomain, UniqueIterator, is_throttled
|
||||
from r2.lib.cache import LocalCache, make_key, MemcachedError
|
||||
import random as rand
|
||||
from r2.models.account import valid_cookie, FakeAccount, valid_feed, valid_admin_cookie
|
||||
from r2.models.account import FakeAccount, valid_feed, valid_admin_cookie
|
||||
from r2.models.subreddit import Subreddit, Frontpage
|
||||
from r2.models import *
|
||||
from errors import ErrorSet, ForbiddenError, errors
|
||||
@@ -39,6 +39,7 @@ from r2.config.extensions import is_api
|
||||
from r2.lib.translation import set_lang
|
||||
from r2.lib.contrib import ipaddress
|
||||
from r2.lib.base import BaseController, proxyurl, abort
|
||||
from r2.lib.authentication import authenticate_user
|
||||
|
||||
from Cookie import CookieError
|
||||
from copy import copy
|
||||
@@ -834,11 +835,7 @@ class RedditController(MinimalController):
|
||||
# no logins for RSS feed unless valid_feed has already been called
|
||||
if not c.user:
|
||||
if c.extension != "rss":
|
||||
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
|
||||
authenticate_user()
|
||||
|
||||
admin_cookie = c.cookies.get(g.admin_cookie)
|
||||
if c.user_is_loggedin and admin_cookie:
|
||||
|
||||
100
r2/r2/lib/authentication.py
Normal file
100
r2/r2/lib/authentication.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# The contents of this file are subject to the Common Public Attribution
|
||||
# License Version 1.0. (the "License"); you may not use this file except in
|
||||
# compliance with the License. You may obtain a copy of the License at
|
||||
# http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
|
||||
# License Version 1.1, but Sections 14 and 15 have been added to cover use of
|
||||
# software over a computer network and provide for limited attribution for the
|
||||
# Original Developer. In addition, Exhibit A has been modified to be consistent
|
||||
# with Exhibit B.
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
||||
# the specific language governing rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is reddit.
|
||||
#
|
||||
# The Original Developer is the Initial Developer. The Initial Developer of
|
||||
# the Original Code is reddit Inc.
|
||||
#
|
||||
# All portions of the code written by reddit are Copyright (c) 2006-2012 reddit
|
||||
# Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
"""Authentication providers for setting c.user on every request.
|
||||
|
||||
This system is intended to allow pluggable authentication for intranets etc. It
|
||||
is not intended to cover all login/logout functionality and in non-cookie
|
||||
scenarios those are probably nonsensical to allow user control of (i.e.
|
||||
single-signon on an intranet doesn't generally allow new account creation on a
|
||||
single website.)
|
||||
"""
|
||||
|
||||
from pylons import g, c
|
||||
|
||||
from r2.models import Account, NotFound
|
||||
from r2.lib.utils import constant_time_compare
|
||||
|
||||
|
||||
_AUTHENTICATION_PROVIDERS = {}
|
||||
|
||||
|
||||
def authentication_provider(allow_logout):
|
||||
"""Register an authentication provider with the framework.
|
||||
|
||||
Authentication providers should return None if authentication failed or an
|
||||
Account object if it succeeded.
|
||||
"""
|
||||
def authentication_provider_decorator(fn):
|
||||
_AUTHENTICATION_PROVIDERS[fn.__name__] = fn
|
||||
fn.allow_logout = allow_logout
|
||||
return fn
|
||||
return authentication_provider_decorator
|
||||
|
||||
|
||||
@authentication_provider(allow_logout=True)
|
||||
def cookie():
|
||||
"""Authenticate the user given a session cookie."""
|
||||
session_cookie = c.cookies.get(g.login_cookie)
|
||||
if not session_cookie:
|
||||
return None
|
||||
|
||||
cookie = session_cookie.value
|
||||
try:
|
||||
uid, timestr, hash = cookie.split(",")
|
||||
uid = int(uid)
|
||||
except:
|
||||
return None
|
||||
|
||||
try:
|
||||
account = Account._byID(uid, data=True)
|
||||
except NotFound:
|
||||
return None
|
||||
|
||||
if not constant_time_compare(cookie, account.make_cookie(timestr)):
|
||||
return None
|
||||
return account
|
||||
|
||||
|
||||
def _get_authenticator():
|
||||
"""Return the configured authenticator."""
|
||||
return _AUTHENTICATION_PROVIDERS[g.authentication_provider]
|
||||
|
||||
|
||||
def user_can_log_out():
|
||||
"""Return if the configured authenticator allows users to log out."""
|
||||
authenticator = _get_authenticator()
|
||||
return authenticator.allow_logout
|
||||
|
||||
|
||||
def authenticate_user():
|
||||
"""Attempt to authenticate the user using the configured authenticator."""
|
||||
|
||||
if not g.read_only_mode:
|
||||
authenticator = _get_authenticator()
|
||||
c.user = authenticator()
|
||||
|
||||
if c.user and c.user._deleted:
|
||||
c.user = None
|
||||
else:
|
||||
c.user = None
|
||||
|
||||
c.user_is_loggedin = bool(c.user)
|
||||
@@ -20,6 +20,8 @@
|
||||
# Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
import base64
|
||||
|
||||
from urllib import unquote_plus
|
||||
from urllib2 import urlopen
|
||||
from urlparse import urlparse, urlunparse
|
||||
@@ -39,6 +41,7 @@ from pylons.i18n import ungettext, _
|
||||
from r2.lib.filters import _force_unicode, _force_utf8
|
||||
from mako.filters import url_escape
|
||||
from r2.lib.contrib import ipaddress
|
||||
from r2.lib.require import require, require_split
|
||||
import snudown
|
||||
|
||||
from r2.lib.utils._utils import *
|
||||
@@ -1416,7 +1419,7 @@ def parse_http_basic(authorization_header):
|
||||
|
||||
Raises RequirementException if anything is uncool.
|
||||
"""
|
||||
auth_scheme, auth_token = require_split(auth, 2)
|
||||
auth_scheme, auth_token = require_split(authorization_header, 2)
|
||||
require(auth_scheme.lower() == "basic")
|
||||
try:
|
||||
auth_data = base64.b64decode(auth_token)
|
||||
|
||||
@@ -608,28 +608,6 @@ class FakeAccount(Account):
|
||||
pref_no_profanity = True
|
||||
|
||||
|
||||
def valid_cookie(cookie):
|
||||
try:
|
||||
uid, timestr, hash = cookie.split(',')
|
||||
uid = int(uid)
|
||||
except:
|
||||
return False
|
||||
|
||||
if g.read_only_mode:
|
||||
return False
|
||||
|
||||
try:
|
||||
account = Account._byID(uid, True)
|
||||
if account._deleted:
|
||||
return False
|
||||
except NotFound:
|
||||
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, None)
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<%!
|
||||
from r2.lib.template_helpers import static, s3_https_if_secure
|
||||
from r2.models.subreddit import DefaultSR
|
||||
from r2.lib import authentication
|
||||
|
||||
%>
|
||||
<%namespace file="utils.html" import="plain_link, img_link, separator, logout"/>
|
||||
@@ -95,9 +96,11 @@
|
||||
<div class="menuitem">
|
||||
${plain_link("submit", "/submit", _sr_path=False)}
|
||||
</div>
|
||||
%if authentication.user_can_log_out():
|
||||
<div class="menuitem bottm-bar">
|
||||
${logout()}
|
||||
</div>
|
||||
% endif
|
||||
%else:
|
||||
<div class="menuitem">
|
||||
${plain_link("login", "/login", _sr_path=False)}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
from r2.models import Sub, FakeSubreddit
|
||||
from r2.models.subreddit import DefaultSR
|
||||
from r2.lib.pages import SearchForm
|
||||
from r2.lib import authentication
|
||||
%>
|
||||
<%namespace file="utils.html" import="plain_link, img_link, text_with_links, separator, logout"/>
|
||||
|
||||
@@ -113,7 +114,7 @@
|
||||
%endif
|
||||
%endif
|
||||
${thing.corner_buttons()}
|
||||
%if c.user_is_loggedin:
|
||||
%if c.user_is_loggedin and authentication.user_can_log_out():
|
||||
${separator("|")}
|
||||
${logout()}
|
||||
%endif
|
||||
|
||||
Reference in New Issue
Block a user