ckanext-privatedatasets/ckanext/privatedatasets/plugin.py

306 lines
12 KiB
Python
Raw Normal View History

import ckan.lib.helpers as helpers
import ckan.logic.auth as logic_auth
2014-06-23 17:25:37 +02:00
import ckan.plugins as p
import ckan.plugins.toolkit as tk
import ckan.new_authz as new_authz
from ckan.common import _, request
2014-06-23 17:25:37 +02:00
######################################################################
########################### AUTH FUNCTIONS ###########################
######################################################################
@tk.auth_allow_anonymous_access
2014-06-23 17:25:37 +02:00
def package_show(context, data_dict):
user = context.get('user')
user_obj = context.get('auth_user_obj')
package = logic_auth.get_package_object(context, data_dict)
# datasets can be readed by it creator
if package and user_obj and package.creator_user_id == user_obj.id:
2014-06-23 17:25:37 +02:00
return {'success': True}
2014-06-26 16:37:27 +02:00
# Not active packages can only be seen by its owners
if package.state == 'active':
# anyone can see a public package
if not package.private:
return {'success': True}
# if the user has rights to read in the organization or in the group
if package.owner_org:
authorized = new_authz.has_user_permission_for_group_or_org(
package.owner_org, user, 'read')
else:
authorized = False
# if the user is not authorized yet, we should check if the
# user is in the allowed_users object
if not authorized:
if hasattr(package, 'extras') and 'allowed_users' in package.extras:
allowed_users = package.extras['allowed_users']
2014-07-03 11:13:02 +02:00
if allowed_users != '': # ''.split(',') ==> ['']
allowed_users_list = allowed_users.split(',')
if user in allowed_users_list:
authorized = True
2014-06-26 16:37:27 +02:00
if not authorized:
# Show a flash message with the URL to adquire the dataset
# This message only can be shown when the user tries to access the dataset via its URL (/dataset/...)
# The message cannot be displayed in other pages that uses the package_show function such as
# the user profile page
2014-07-03 11:13:02 +02:00
if hasattr(package, 'extras') and 'adquire_url' in package.extras and request.path.startswith('/dataset/')\
and package.extras['adquire_url'] != '':
2014-06-26 16:37:27 +02:00
helpers.flash_notice(_('This private dataset can be adquired. To do so, please click ' +
'<a target="_blank" href="%s">here</a>') % package.extras['adquire_url'],
allow_html=True)
return {'success': False, 'msg': _('User %s not authorized to read package %s') % (user, package.id)}
else:
return {'success': True}
2014-06-23 17:25:37 +02:00
else:
return {'success': False, 'msg': _('User %s not authorized to read package %s') % (user, package.id)}
def package_update(context, data_dict):
user = context.get('user')
user_obj = context.get('auth_user_obj')
package = logic_auth.get_package_object(context, data_dict)
# Only the package creator can update it
if package and user_obj and package.creator_user_id == user_obj.id:
return {'success': True}
# if the user has rights to update a dataset in the organization or in the group
if package and package.owner_org:
authorized = new_authz.has_user_permission_for_group_or_org(
package.owner_org, user, 'update_dataset')
else:
authorized = False
if not authorized:
return {'success': False, 'msg': _('User %s is not authorized to edit package %s') % (user, package.id)}
else:
return {'success': True}
@tk.auth_allow_anonymous_access
def resource_show(context, data_dict):
# This function is needed since CKAN resource_show function uses the default package_show
# function instead the one defined in the plugin.
# A bug is openend in order to be able to remove this function
# https://github.com/ckan/ckan/issues/1818
model = context['model']
user = context.get('user')
resource = logic_auth.get_resource_object(context, data_dict)
# check authentication against package
query = model.Session.query(model.Package)\
.join(model.ResourceGroup)\
.join(model.Resource)\
.filter(model.ResourceGroup.id == resource.resource_group_id)
pkg = query.first()
if not pkg:
raise tk.ObjectNotFound(_('No package found for this resource, cannot check auth.'))
pkg_dict = {'id': pkg.id}
authorized = package_show(context, pkg_dict).get('success')
if not authorized:
return {'success': False, 'msg': _('User %s not authorized to read resource %s') % (user, resource.id)}
else:
return {'success': True}
######################################################################
############################### CHECKER ##############################
######################################################################
def private_datasets_metadata_checker(key, data, errors, context):
2014-06-23 17:25:37 +02:00
# TODO: In some cases, we will need to retireve all the dataset information if it isn't present...
2014-06-24 11:04:14 +02:00
private_val = data.get(('private',))
private = private_val is True if isinstance(private_val, bool) else private_val == "True"
metadata_value = data[key]
2014-06-23 17:25:37 +02:00
2014-06-24 11:04:14 +02:00
# If allowed users are included and the dataset is not private outside and organization, an error will be raised.
if metadata_value != '' and not private:
errors[key].append(_('This field is only valid when you create a private dataset outside an organization'))
2014-06-23 17:25:37 +02:00
class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
2014-06-23 17:25:37 +02:00
p.implements(p.IDatasetForm)
p.implements(p.IAuthFunctions)
p.implements(p.IConfigurer)
p.implements(p.IRoutes, inherit=True)
p.implements(p.IActions)
p.implements(p.IPackageController)
2014-06-23 17:25:37 +02:00
######################################################################
############################ DATASET FORM ############################
######################################################################
def _modify_package_schema(self):
return {
2014-06-26 16:37:27 +02:00
# remove datasets_with_no_organization_cannot_be_private validator
'private': [tk.get_validator('ignore_missing'),
tk.get_validator('boolean_validator')],
2014-06-23 17:25:37 +02:00
'allowed_users': [tk.get_validator('ignore_missing'),
private_datasets_metadata_checker,
tk.get_converter('convert_to_extras')],
'adquire_url': [tk.get_validator('ignore_missing'),
private_datasets_metadata_checker,
tk.get_converter('convert_to_extras')],
'searchable': [tk.get_validator('ignore_missing'),
private_datasets_metadata_checker,
tk.get_converter('convert_to_extras'),
tk.get_validator('boolean_validator')]
2014-06-23 17:25:37 +02:00
}
def create_package_schema(self):
2014-06-24 11:04:14 +02:00
# grab the default schema in our plugin
2014-06-23 17:25:37 +02:00
schema = super(PrivateDatasets, self).create_package_schema()
schema.update(self._modify_package_schema())
return schema
def update_package_schema(self):
2014-06-24 11:04:14 +02:00
# grab the default schema in our plugin
2014-06-23 17:25:37 +02:00
schema = super(PrivateDatasets, self).update_package_schema()
schema.update(self._modify_package_schema())
return schema
def show_package_schema(self):
schema = super(PrivateDatasets, self).show_package_schema()
schema.update({
'allowed_users': [tk.get_converter('convert_from_extras'),
tk.get_validator('ignore_missing')],
'adquire_url': [tk.get_converter('convert_from_extras'),
tk.get_validator('ignore_missing')],
'searchable': [tk.get_converter('convert_from_extras'),
tk.get_validator('ignore_missing')]
2014-06-23 17:25:37 +02:00
})
return schema
def is_fallback(self):
# Return True to register this plugin as the default handler for
# package types not handled by any other IDatasetForm plugin.
return True
def package_types(self):
# This plugin doesn't handle any special package types, it just
# registers itself as the default (above).
return []
######################################################################
########################### AUTH FUNCTIONS ###########################
######################################################################
def get_auth_functions(self):
return {'package_show': package_show,
'package_update': package_update,
'resource_show': resource_show}
2014-06-23 17:25:37 +02:00
######################################################################
############################ ICONFIGURER #############################
######################################################################
def update_config(self, config):
# Add this plugin's templates dir to CKAN's extra_template_paths, so
# that CKAN will use this plugin's custom templates.
tk.add_template_directory(config, 'templates')
# Register this plugin's fanstatic directory with CKAN.
tk.add_resource('fanstatic', 'privatedatasets')
######################################################################
############################### ROUTES ###############################
######################################################################
def after_map(self, m):
# DataSet adquired notification
m.connect('/dataset_adquired',
controller='ckanext.privatedatasets.controller:AdquiredDatasetsController',
action='add_users', conditions=dict(method=['POST']))
return m
######################################################################
############################## IACTIONS ##############################
######################################################################
def get_actions(self):
# Update package_search function. When the URL is the URL used to
# retrieve the datasets, the context parameter will me modified and
# the field 'ignore_capacity_check' will be added in order to
# get both the private and the public datasets.
_old_package_search = tk.get_action('package_search')
@tk.side_effect_free
def _new_package_search(context, data_dict):
valid_urls = ['/dataset', '/api/3/action/package_search',
'/api/3/action/dataset_search']
if request.path in valid_urls:
context.update({'ignore_capacity_check': True})
return _old_package_search(context, data_dict)
_new_package_search.__doc__ = _old_package_search.__doc__
# Modify the package_show function used across the system
return {'package_search': _new_package_search}
######################################################################
######################### IPACKAGECONTROLLER #########################
######################################################################
def before_index(self, pkg_dict):
return pkg_dict
def before_view(self, pkg_dict):
return pkg_dict
def before_search(self, search_params):
# Filter mustn't be applied when a user serachs datasets within organizations
if ('q' not in search_params or 'owner_org' not in search_params['q']) and \
('fq' not in search_params or 'owner_org' not in search_params['fq']):
# Create the fq field if it does not exist
if 'fq' not in search_params:
search_params['fq'] = ''
# searchable does not exist or is equals to True
search_params['fq'] += ' -(-searchable:True AND searchable:[* TO *])'
return search_params
def create(self, pkg_dict):
return pkg_dict
def edit(self, pkg_dict):
return pkg_dict
def read(self, pkg_dict):
return pkg_dict
def delete(self, pkg_dict):
return pkg_dict
def after_create(self, context, pkg_dict):
return pkg_dict
def after_update(self, context, pkg_dict):
return pkg_dict
def after_show(self, context, pkg_dict):
return pkg_dict
def after_search(self, search_results, search_params):
return search_results
def after_delete(self, context, pkg_dict):
return pkg_dict