Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/okfn/ckan
Browse files Browse the repository at this point in the history
  • Loading branch information
rossjones committed May 3, 2012
2 parents 96e486d + d7aad92 commit 9d460be
Show file tree
Hide file tree
Showing 16 changed files with 294 additions and 128 deletions.
14 changes: 7 additions & 7 deletions ckan/config/environment.py
Expand Up @@ -30,12 +30,12 @@ class _Helpers(object):
def __init__(self, helpers, restrict=True):
functions = {}
allowed = helpers.__allowed_functions__
# list of functions due to be depreciated
self.depreciated = []
# list of functions due to be deprecated
self.deprecated = []

for helper in dir(helpers):
if helper not in allowed:
self.depreciated.append(helper)
self.deprecated.append(helper)
if restrict:
continue
functions[helper] = getattr(helpers, helper)
Expand Down Expand Up @@ -63,14 +63,14 @@ def null_function(cls, *args, **kw):
def __getattr__(self, name):
''' return the function/object requested '''
if name in self.functions:
if name in self.depreciated:
msg = 'Template helper function `%s` is depriciated' % name
if name in self.deprecated:
msg = 'Template helper function `%s` is deprecated' % name
self.log.warn(msg)
return self.functions[name]
else:
if name in self.depreciated:
if name in self.deprecated:
msg = 'Template helper function `%s` is not available ' \
'as it has been depriciated.\nYou can enable it ' \
'as it has been deprecated.\nYou can enable it ' \
'by setting ckan.restrict_template_vars = true ' \
'in your .ini file.' % name
self.log.critical(msg)
Expand Down
2 changes: 1 addition & 1 deletion ckan/controllers/home.py
Expand Up @@ -14,8 +14,8 @@ class HomeController(BaseController):
repo = model.repo

def __before__(self, action, **env):
BaseController.__before__(self, action, **env)
try:
BaseController.__before__(self, action, **env)
context = {'model':model,'user': c.user or c.author}
ckan.logic.check_access('site_read',context)
except ckan.logic.NotAuthorized:
Expand Down
14 changes: 11 additions & 3 deletions ckan/controllers/user.py
Expand Up @@ -52,6 +52,11 @@ def _setup_template_variables(self, context):

## end hooks

def _get_repoze_handler(self, handler_name):
'''Returns the URL that repoze.who will respond to and perform a
login or logout.'''
return getattr(request.environ['repoze.who.plugins']['friendlyform'], handler_name)

def index(self):
LIMIT = 20

Expand Down Expand Up @@ -165,7 +170,9 @@ def _save_new(self, context):
return self.new(data_dict, errors, error_summary)
if not c.user:
# Redirect to a URL picked up by repoze.who which performs the login
h.redirect_to('/login_generic?login=%s&password=%s' % (
login_url = self._get_repoze_handler('login_handler_path')
h.redirect_to('%s?login=%s&password=%s' % (
login_url,
str(data_dict['name']),
quote(data_dict['password1'].encode('utf-8'))))
else:
Expand Down Expand Up @@ -257,6 +264,7 @@ def login(self):
g.openid_enabled = False

if not c.user:
c.login_handler = h.url_for(self._get_repoze_handler('login_handler_path'))
return render('user/login.html')
else:
return render('user/logout_first.html')
Expand All @@ -283,10 +291,10 @@ def logged_in(self):
h.redirect_to(locale=lang, controller='user', action='login')

def logout(self):
# save our language in the session so we don't loose it
# save our language in the session so we don't lose it
session['lang'] = request.environ.get('CKAN_LANG')
session.save()
h.redirect_to('/user/logout')
h.redirect_to(self._get_repoze_handler('logout_handler_path'))

def set_lang(self, lang):
# this allows us to set the lang in session. Used for logging
Expand Down
67 changes: 37 additions & 30 deletions ckan/lib/base.py
Expand Up @@ -169,9 +169,11 @@ def _identify_user(self):
b) For API calls he may set a header with his API key.
If the user is identified then:
c.user = user name (unicode)
c.userobj = user object
c.author = user name
otherwise:
c.user = None
c.userobj = None
c.author = user\'s IP address (unicode)
'''
# see if it was proxied first
Expand All @@ -180,8 +182,10 @@ def _identify_user(self):
c.remote_addr = request.environ.get('REMOTE_ADDR', 'Unknown IP Address')

# environ['REMOTE_USER'] is set by repoze.who if it authenticates a user's
# cookie or OpenID. (But it doesn't check the user (still) exists in our
# database - we need to do that here.
# cookie or OpenID. But repoze.who doesn't check the user (still)
# exists in our database - we need to do that here. (Another way would
# be with an userid_checker, but that would mean another db access.
# See: http://docs.repoze.org/who/1.0/narr.html#module-repoze.who.plugins.sql )
c.user = request.environ.get('REMOTE_USER', '')
if c.user:
c.user = c.user.decode('utf8')
Expand Down Expand Up @@ -210,38 +214,41 @@ def __call__(self, environ, start_response):
# the request is routed to. This routing information is
# available in environ['pylons.routes_dict']

try:
res = WSGIController.__call__(self, environ, start_response)
finally:
model.Session.remove()

# Clean out any old cookies as they may contain api keys etc
# This also improves the cachability of our pages as cookies
# prevent proxy servers from caching content unless they have
# been configured to ignore them.
# we do not want to clear cookies when setting the user lang
if not environ.get('PATH_INFO').startswith('/user/set_lang'):
for cookie in request.cookies:
if cookie.startswith('ckan') and cookie not in ['ckan']:
response.delete_cookie(cookie)
# Remove the ckan session cookie if not used e.g. logged out
elif cookie == 'ckan' and not c.user:
# Check session for valid data (including flash messages)
# (DGU also uses session for a shopping basket-type behaviour)
is_valid_cookie_data = False
for key, value in session.items():
if not key.startswith('_') and value:
is_valid_cookie_data = True
break
if not is_valid_cookie_data:
if session.id:
if not session.get('lang'):
session.delete()
else:
response.delete_cookie(cookie)
# Remove auth_tkt repoze.who cookie if user not logged in.
elif cookie == 'auth_tkt' and not session.id:
response.delete_cookie(cookie)

try:
return WSGIController.__call__(self, environ, start_response)
finally:
model.Session.remove()
for cookie in request.cookies:
if cookie.startswith('ckan') and cookie not in ['ckan']:
response.delete_cookie(cookie)
# Remove the ckan session cookie if not used e.g. logged out
elif cookie == 'ckan' and not c.user:
# Check session for valid data (including flash messages)
# (DGU also uses session for a shopping basket-type behaviour)
is_valid_cookie_data = False
for key, value in session.items():
if not key.startswith('_') and value:
is_valid_cookie_data = True
break
if not is_valid_cookie_data:
if session.id:
if not session.get('lang'):
self.log.debug('No session data any more - deleting session')
self.log.debug('Session: %r', session.items())
session.delete()
else:
response.delete_cookie(cookie)
self.log.debug('No session data any more - deleting session cookie')
# Remove auth_tkt repoze.who cookie if user not logged in.
elif cookie == 'auth_tkt' and not session.id:
response.delete_cookie(cookie)

return res

def __after__(self, action, **params):
self._set_cors()
Expand Down
14 changes: 9 additions & 5 deletions ckan/lib/cli.py
Expand Up @@ -3,11 +3,15 @@
import sys
import logging
from pprint import pprint
import re

import paste.script
from paste.registry import Registry
from paste.script.util.logging_config import fileConfig
import re

#NB No CKAN imports are allowed until after the config file is loaded.
# i.e. do the imports in methods, after _load_config is called.
# Otherwise loggers get disabled.

class MockTranslator(object):
def gettext(self, value):
Expand All @@ -33,11 +37,7 @@ class CkanCommand(paste.script.command.Command):
group_name = 'ckan'

def _load_config(self):
# Avoids vdm logging warning
logging.basicConfig(level=logging.ERROR)

from paste.deploy import appconfig
from ckan.config.environment import load_environment
if not self.options.config:
msg = 'No config file supplied'
raise self.BadCommand(msg)
Expand All @@ -46,6 +46,10 @@ def _load_config(self):
raise AssertionError('Config filename %r does not exist.' % self.filename)
fileConfig(self.filename)
conf = appconfig('config:' + self.filename)
assert 'ckan' not in dir() # otherwise loggers would be disabled
# We have now loaded the config. Now we can import ckan for the
# first time.
from ckan.config.environment import load_environment
load_environment(conf.global_conf, conf.local_conf)

self.registry=Registry()
Expand Down
78 changes: 61 additions & 17 deletions ckan/lib/helpers.py
Expand Up @@ -263,7 +263,7 @@ def are_there_flash_messages():

def nav_link(*args, **kwargs):
# nav_link() used to need c passing as the first arg
# this is depriciated as pointless
# this is deprecated as pointless
# throws error if ckan.restrict_template_vars is True
# When we move to strict helpers then this should be removed as a wrapper
if len(args) > 2 or (len(args) > 1 and 'controller' in kwargs):
Expand All @@ -286,7 +286,7 @@ def _nav_link(text, controller, **kwargs):

def nav_named_link(*args, **kwargs):
# subnav_link() used to need c passing as the first arg
# this is depriciated as pointless
# this is deprecated as pointless
# throws error if ckan.restrict_template_vars is True
# When we move to strict helpers then this should be removed as a wrapper
if len(args) > 3 or (len(args) > 0 and 'text' in kwargs) or \
Expand All @@ -307,7 +307,7 @@ def _nav_named_link(text, name, **kwargs):

def subnav_link(*args, **kwargs):
# subnav_link() used to need c passing as the first arg
# this is depriciated as pointless
# this is deprecated as pointless
# throws error if ckan.restrict_template_vars is True
# When we move to strict helpers then this should be removed as a wrapper
if len(args) > 2 or (len(args) > 1 and 'action' in kwargs):
Expand All @@ -325,7 +325,7 @@ def _subnav_link(text, action, **kwargs):

def subnav_named_route(*args, **kwargs):
# subnav_link() used to need c passing as the first arg
# this is depriciated as pointless
# this is deprecated as pointless
# throws error if ckan.restrict_template_vars is True
# When we move to strict helpers then this should be removed as a wrapper
if len(args) > 2 or (len(args) > 0 and 'text' in kwargs) or \
Expand Down Expand Up @@ -382,7 +382,7 @@ def facet_items(*args, **kwargs):
"""
_log.warning('Deprecated function: ckan.lib.helpers:facet_items(). Will be removed in v1.8')
# facet_items() used to need c passing as the first arg
# this is depriciated as pointless
# this is deprecated as pointless
# throws error if ckan.restrict_template_vars is True
# When we move to strict helpers then this should be removed as a wrapper
if len(args) > 2 or (len(args) > 0 and 'name' in kwargs) or (len(args) > 1 and 'limit' in kwargs):
Expand Down Expand Up @@ -605,26 +605,69 @@ def date_str_to_datetime(date_str):
# a strptime. Also avoids problem with Python 2.5 not having %f.
return datetime.datetime(*map(int, re.split('[^\d]', date_str)))

def parse_rfc_2822_date(date_str, tz_aware=True):
def parse_rfc_2822_date(date_str, assume_utc=True):
"""
Parse a date string of the form specified in RFC 2822, and return a datetime.
RFC 2822 is the date format used in HTTP headers.
If the date string contains a timezone indication, and tz_aware is True,
then the associated tzinfo is attached to the returned datetime object.
Returns None if the string cannot be parse as a valid datetime.
RFC 2822 is the date format used in HTTP headers. It should contain timezone
information, but that cannot be relied upon.
If date_str doesn't contain timezone information, then the 'assume_utc' flag
determines whether we assume this string is local (with respect to the
server running this code), or UTC. In practice, what this means is that if
assume_utc is True, then the returned datetime is 'aware', with an associated
tzinfo of offset zero. Otherwise, the returned datetime is 'naive'.
If timezone information is available in date_str, then the returned datetime
is 'aware', ie - it has an associated tz_info object.
Returns None if the string cannot be parsed as a valid datetime.
"""
time_tuple = email.utils.parsedate_tz(date_str)

# Not parsable
if not time_tuple:
return None

if not tz_aware:
time_tuple = time_tuple[:-1] + (None,)
# No timezone information available in the string
if time_tuple[-1] is None and not assume_utc:
return datetime.datetime.fromtimestamp(email.utils.mktime_tz(time_tuple))
else:
offset = 0 if time_tuple[-1] is None else time_tuple[-1]
tz_info = _RFC2282TzInfo(offset)
return datetime.datetime(*time_tuple[:6], microsecond=0, tzinfo=tz_info)

class _RFC2282TzInfo(datetime.tzinfo):
"""
A datetime.tzinfo implementation used by parse_rfc_2822_date() function.
In order to return timezone information, a concrete implementation of
datetime.tzinfo is required. This class represents tzinfo that knows
about it's offset from UTC, has no knowledge of daylight savings time, and
no knowledge of the timezone name.
"""

def __init__(self, offset):
"""
offset from UTC in seconds.
"""
self.offset = datetime.timedelta(seconds=offset)

def utcoffset(self, dt):
return self.offset

def dst(self, dt):
"""
Dates parsed from an RFC 2822 string conflate timezone and dst, and so
it's not possible to determine whether we're in DST or not, hence
returning None.
"""
return None

def tzname(self, dt):
return None

return datetime.datetime.fromtimestamp(email.utils.mktime_tz(time_tuple))

def time_ago_in_words_from_str(date_str, granularity='month'):
if date_str:
Expand Down Expand Up @@ -690,7 +733,7 @@ def dump_json(obj, **kw):

def auto_log_message(*args):
# auto_log_message() used to need c passing as the first arg
# this is depriciated as pointless
# this is deprecated as pointless
# throws error if ckan.restrict_template_vars is True
# When we move to strict helpers then this should be removed as a wrapper
if len(args) and asbool(config.get('ckan.restrict_template_vars', 'false')):
Expand Down Expand Up @@ -777,7 +820,7 @@ def process_names(items):
'default_group_type',
'facet_items',
'facet_title',
# am_authorized, # depreciated
# am_authorized, # deprecated
'check_access',
'linked_user',
'linked_authorization_group',
Expand Down Expand Up @@ -809,6 +852,7 @@ def process_names(items):
'convert_to_dict',
'activity_div',
'lang_native_name',
'unselected_facet_items',
# imported into ckan.lib.helpers
'literal',
'link_to',
Expand Down
2 changes: 1 addition & 1 deletion ckan/logic/__init__.py
Expand Up @@ -227,7 +227,7 @@ def get_action(action):

def get_or_bust(data_dict, keys):
'''Try and get values from dictionary and if they are not there
raise a validataion error.
raise a validation error.
data_dict: a dictionary
keys: either a single string key in which case will return a single value,
Expand Down

0 comments on commit 9d460be

Please sign in to comment.