diff --git a/r2/r2/config/routing.py b/r2/r2/config/routing.py index 3568e1a4f..6c7d376de 100644 --- a/r2/r2/config/routing.py +++ b/r2/r2/config/routing.py @@ -47,6 +47,7 @@ def make_map(global_conf={}, app_conf={}): mc('/sup', controller='front', action='sup') mc('/traffic', controller='front', action='site_traffic') + mc('/account-activity', controller='front', action='account_activity') mc('/about/message/:where', controller='message', action='listing') mc('/about/:location', controller='front', diff --git a/r2/r2/controllers/front.py b/r2/r2/controllers/front.py index 1e402352d..f87eae038 100644 --- a/r2/r2/controllers/front.py +++ b/r2/r2/controllers/front.py @@ -786,6 +786,11 @@ class FrontController(RedditController): return BoringPage("traffic", content = RedditTraffic()).render() + @validate(VUser()) + def GET_account_activity(self): + return AccountActivityPage().render() + + class FormsController(RedditController): def GET_password(self): @@ -1096,4 +1101,3 @@ class FormsController(RedditController): giftmessage, passthrough) ).render() - diff --git a/r2/r2/lib/pages/pages.py b/r2/r2/lib/pages/pages.py index d71b7fe09..a74c96f65 100644 --- a/r2/r2/lib/pages/pages.py +++ b/r2/r2/lib/pages/pages.py @@ -57,6 +57,14 @@ import graph, pycountry, time from itertools import chain from urllib import quote +# the ip tracking code is currently deeply tied with spam prevention stuff +# this will be open sourced as soon as it can be decoupled +try: + from r2admin.lib.ip_events import ips_by_account_id +except ImportError: + def ips_by_account_id(account_id): + return [] + from things import wrap_links, default_thing_wrapper datefmt = _force_utf8(_('%d %b %Y')) @@ -284,6 +292,10 @@ class Reddit(Templated): ps.append(SideContentBox(_("Recently viewed links"), [ClickGadget(c.recent_clicks)])) + if c.user_is_loggedin: + activity_link = AccountActivityBox() + ps.append(activity_link) + return ps def render(self, *a, **kw): @@ -370,6 +382,10 @@ class Reddit(Templated): """returns a Wrapped (or renderable) item for the main content div.""" return self.content_stack((self.infobar, self.nav_menu, self._content)) +class AccountActivityBox(Templated): + def __init__(self): + super(AccountActivityBox, self).__init__() + class RedditHeader(Templated): def __init__(self): pass @@ -1069,6 +1085,7 @@ def votes_visible(user): user.pref_public_votes or c.user_is_admin) + class ProfilePage(Reddit): """Container for a user's profile page. As such, the Account object of the user must be passed in as the first argument, along @@ -3549,3 +3566,15 @@ class TryCompact(Reddit): u.set_extension("mobile") self.mobile = u.unparse() Reddit.__init__(self, **kw) + +class AccountActivityPage(BoringPage): + def __init__(self): + super(AccountActivityPage, self).__init__(_("account activity")) + + def content(self): + return UserIPHistory() + +class UserIPHistory(Templated): + def __init__(self): + self.ips = ips_by_account_id(c.user._id) + super(UserIPHistory, self).__init__() diff --git a/r2/r2/lib/strings.py b/r2/r2/lib/strings.py index 94ce55fe0..c5be8f45c 100644 --- a/r2/r2/lib/strings.py +++ b/r2/r2/lib/strings.py @@ -154,6 +154,9 @@ string_dict = dict( gold_summary_signed_gift = _("You're about to give %(amount)s of reddit gold to %(recipient)s, who will be told that it came from you."), gold_summary_anonymous_gift = _("You're about to give %(amount)s of reddit gold to %(recipient)s. It will be an anonymous gift."), unvotable_message = _("sorry, this has been archived and can no longer be voted on"), + account_activity_blurb = _("This page shows a history of recent activity on your account. If you notice unusual activity, you should change your password immediately. Location information is guessed from your computer's IP address and may be wildly wrong, especially for visits from mobile devices."), + your_current_ip_is = _("You are currently accessing reddit from this IP address: %(address)s."), + ) class StringHandler(object): diff --git a/r2/r2/public/static/css/reddit.css b/r2/r2/public/static/css/reddit.css index 32212aa0b..c6b25c7f5 100644 --- a/r2/r2/public/static/css/reddit.css +++ b/r2/r2/public/static/css/reddit.css @@ -510,7 +510,23 @@ ul.flat-vert {text-align: left;} font-size: 110%; } +.account-activity-box { + text-align: center; +} +#account-activity table { + margin: 2em 0 0 2em; + width: 45em; + font-size: larger; +} + +#account-activity th { + font-weight: bold; +} + +#account-activity td { + padding: .5em 0; +} .infotable { margin-top: 5px; margin-bottom: 10px; } .infotable .small { font-size: smaller; } diff --git a/r2/r2/templates/accountactivitybox.html b/r2/r2/templates/accountactivitybox.html new file mode 100644 index 000000000..f45e3ce2b --- /dev/null +++ b/r2/r2/templates/accountactivitybox.html @@ -0,0 +1,25 @@ +## 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 CondeNet, Inc. +## +## All portions of the code written by CondeNet are Copyright (c) 2006-2010 +## CondeNet, Inc. All Rights Reserved. +############################################################################### + +
diff --git a/r2/r2/templates/useriphistory.html b/r2/r2/templates/useriphistory.html new file mode 100644 index 000000000..5fed1c4ed --- /dev/null +++ b/r2/r2/templates/useriphistory.html @@ -0,0 +1,55 @@ +## 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 CondeNet, Inc. +## +## All portions of the code written by CondeNet are Copyright (c) 2006-2010 +## CondeNet, Inc. All Rights Reserved. +################################################################################ + +<%namespace file="utils.html" import="timestamp"/> +<% + from r2.lib.strings import strings + ip_format = {'address': request.ip} +%> + +${strings.account_activity_blurb}
+ +${strings.your_current_ip_is % ip_format}
+ +| ${_("IP address")} | +${_("Location")} | +${_("Last Visit")} | +
|---|---|---|
| ${ip} | +${location.get('country_name', '')} | +${timestamp(last_visit)} ${_('ago')} | +