# -*- coding: utf-8 -*- # Copyright (c) 2014 - 2017 CoNWeT Lab., Universidad Politécnica de Madrid # Copyright (c) 2018 Future Internet Consulting and Development Solutions S.L. # This file is part of CKAN Private Dataset Extension. # CKAN Private Dataset Extension is free software: you can redistribute it and/or # modify it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # CKAN Private Dataset Extension is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with CKAN Private Dataset Extension. If not, see . from __future__ import absolute_import, unicode_literals from ckan import model, plugins as p from ckan.lib import search from ckan.lib.plugins import DefaultPermissionLabels from ckan.plugins import toolkit as tk from flask import Blueprint from ckanext.privatedatasets import auth, actions, constants, converters_validators as conv_val, db, helpers from ckanext.privatedatasets.views import acquired_datasets HIDDEN_FIELDS = [constants.ALLOWED_USERS, constants.SEARCHABLE] class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm, DefaultPermissionLabels): p.implements(p.IDatasetForm) p.implements(p.IAuthFunctions) p.implements(p.IConfigurer) p.implements(p.IBlueprint) p.implements(p.IActions) p.implements(p.IPackageController, inherit=True) p.implements(p.ITemplateHelpers) p.implements(p.IPermissionLabels) ###################################################################### ############################ DATASET FORM ############################ ###################################################################### def __init__(self, name=None): self.indexer = search.PackageSearchIndex() def _modify_package_schema(self): return { # remove datasets_with_no_organization_cannot_be_private validator 'private': [tk.get_validator('ignore_missing'), tk.get_validator('boolean_validator')], constants.ALLOWED_USERS_STR: [tk.get_validator('ignore_missing'), conv_val.private_datasets_metadata_checker], constants.ALLOWED_USERS: [conv_val.allowed_users_convert, tk.get_validator('ignore_missing'), conv_val.private_datasets_metadata_checker], constants.ACQUIRE_URL: [tk.get_validator('ignore_missing'), conv_val.private_datasets_metadata_checker, conv_val.url_checker, tk.get_converter('convert_to_extras')], constants.SEARCHABLE: [tk.get_validator('ignore_missing'), conv_val.private_datasets_metadata_checker, tk.get_converter('convert_to_extras'), tk.get_validator('boolean_validator')] } def create_package_schema(self): # grab the default schema in our plugin schema = super(PrivateDatasets, self).create_package_schema() schema.update(self._modify_package_schema()) return schema def update_package_schema(self): # grab the default schema in our plugin 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({ constants.ALLOWED_USERS: [conv_val.get_allowed_users, tk.get_validator('ignore_missing')], constants.ACQUIRE_URL: [tk.get_converter('convert_from_extras'), tk.get_validator('ignore_missing')], constants.SEARCHABLE: [tk.get_converter('convert_from_extras'), tk.get_validator('ignore_missing')] }) 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): auth_functions = {'package_show': auth.package_show, 'package_update': auth.package_update, # 'resource_show': auth.resource_show, constants.PACKAGE_ACQUIRED: auth.package_acquired, constants.ACQUISITIONS_LIST: auth.acquisitions_list, constants.PACKAGE_DELETED: auth.revoke_access} # resource_show is not required in CKAN 2.3 because it delegates to # package_show if not tk.check_ckan_version(min_version='2.3'): auth_functions['resource_show'] = auth.resource_show return auth_functions ###################################################################### ############################ 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. if p.toolkit.check_ckan_version(min_version='2.8'): tk.add_template_directory(config, 'templates_2.8') else: tk.add_template_directory(config, 'templates') # Register this plugin's fanstatic directory with CKAN. tk.add_resource(b'fanstatic', b'privatedatasets') ###################################################################### ############################# IBLUEPRINT ############################# ###################################################################### def get_blueprint(self): blueprint = Blueprint('privatedatasets', self.__module__) blueprint.add_url_rule('/dashboard/acquired', 'acquired_datasets', acquired_datasets) return blueprint ###################################################################### ############################## IACTIONS ############################## ###################################################################### def get_actions(self): return { constants.PACKAGE_ACQUIRED: actions.package_acquired, constants.ACQUISITIONS_LIST: actions.acquisitions_list, constants.PACKAGE_DELETED: actions.revoke_access } ###################################################################### ######################### IPACKAGECONTROLLER ######################### ###################################################################### def _delete_pkg_atts(self, pkg_dict, attrs): for attr in attrs: if attr in pkg_dict: del pkg_dict[attr] def before_index(self, pkg_dict): if 'extras_' + constants.SEARCHABLE in pkg_dict: if pkg_dict['extras_searchable'] == 'False': pkg_dict['capacity'] = 'private' else: pkg_dict['capacity'] = 'public' return pkg_dict def after_create(self, context, pkg_dict): session = context['session'] update_cache = False db.init_db(context['model']) # Get the users and the package ID if constants.ALLOWED_USERS in pkg_dict: allowed_users = pkg_dict[constants.ALLOWED_USERS] package_id = pkg_dict['id'] # Get current users users = db.AllowedUser.get(package_id=package_id) # Delete users and save the list of current users current_users = [] for user in users: current_users.append(user.user_name) if user.user_name not in allowed_users: session.delete(user) update_cache = True # Add non existing users for user_name in allowed_users: if user_name not in current_users: out = db.AllowedUser() out.package_id = package_id out.user_name = user_name out.save() session.add(out) update_cache = True session.commit() # The cache should be updated. Otherwise, the system may return # outdated information in future requests if update_cache: new_pkg_dict = tk.get_action('package_show')( {'model': context['model'], 'ignore_auth': True, 'validate': False, 'use_cache': False}, {'id': package_id}) # Prevent acquired datasets jumping to the first position revision = tk.get_action('revision_show')({'ignore_auth': True}, {'id': new_pkg_dict['revision_id']}) new_pkg_dict['metadata_modified'] = revision.get('timestamp', '') self.indexer.update_dict(new_pkg_dict) return pkg_dict def after_update(self, context, pkg_dict): return self.after_create(context, pkg_dict) def after_show(self, context, pkg_dict): user_obj = context.get('auth_user_obj') updating_via_api = context.get(constants.CONTEXT_CALLBACK, False) # allowed_users and searchable fileds can be only viewed by (and only if the dataset is private): # * the dataset creator # * the sysadmin # * users allowed to update the allowed_users list via the notification API if pkg_dict.get('private') is False or not updating_via_api and (not user_obj or (pkg_dict['creator_user_id'] != user_obj.id and not user_obj.sysadmin)): # The original list cannot be modified attrs = list(HIDDEN_FIELDS) self._delete_pkg_atts(pkg_dict, attrs) return pkg_dict def after_delete(self, context, pkg_dict): session = context['session'] package_id = pkg_dict['id'] # Get current users db.init_db(context['model']) users = db.AllowedUser.get(package_id=package_id) # Delete all the users for user in users: session.delete(user) session.commit() return pkg_dict def after_search(self, search_results, search_params): for result in search_results['results']: # Extra fields should not be returned # The original list cannot be modified attrs = list(HIDDEN_FIELDS) # Additionally, resources should not be included if the user is not allowed # to show the resource context = { 'model': model, 'session': model.Session, 'user': tk.c.user, 'user_obj': tk.c.userobj } try: tk.check_access('package_show', context, result) except tk.NotAuthorized: # NotAuthorized exception is risen when the user is not allowed # to read the package. attrs.append('resources') # Delete self._delete_pkg_atts(result, attrs) return search_results #### def get_dataset_labels(self, dataset_obj): labels = super(PrivateDatasets, self).get_dataset_labels( dataset_obj) if getattr(dataset_obj, 'searchable', False): labels.append('searchable') return labels def get_user_dataset_labels(self, user_obj): labels = super(PrivateDatasets, self).get_user_dataset_labels( user_obj) labels.append('searchable') return labels ###################################################################### ######################### ITEMPLATESHELPER ########################### ###################################################################### def get_helpers(self): return {'is_dataset_acquired': helpers.is_dataset_acquired, 'get_allowed_users_str': helpers.get_allowed_users_str, 'is_owner': helpers.is_owner, 'can_read': helpers.can_read, 'show_acquire_url_on_create': helpers.show_acquire_url_on_create, 'show_acquire_url_on_edit': helpers.show_acquire_url_on_edit, 'acquire_button': helpers.acquire_button }