Allowed users are stored in a new table. Create auxiliar files

This commit is contained in:
Aitor Magán 2014-07-10 12:26:31 +02:00
parent 11a0a349f9
commit 1c8340a089
10 changed files with 313 additions and 176 deletions

View File

@ -0,0 +1,109 @@
import ckan.lib.helpers as helpers
import ckan.logic.auth as logic_auth
import ckan.plugins.toolkit as tk
import ckan.new_authz as new_authz
import db
from ckan.common import _, request
@tk.auth_allow_anonymous_access
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:
return {'success': True}
# 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:
# Init the model
if db.package_allowed_users_table is None:
db.init_db(context['model'])
if db.AllowedUser.get(package_id=package.id, user_name=user):
authorized = True
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
if hasattr(package, 'extras') and 'adquire_url' in package.extras and request.path.startswith('/dataset/')\
and package.extras['adquire_url'] != '':
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}
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}

View File

@ -1,6 +1,8 @@
import ckan.lib.base as base
import ckan.lib.helpers as helpers
import ckan.model as model
import ckan.plugins as plugins
import ckanext.privatedatasets.db as db
import importlib
import logging
import pylons.config as config
@ -27,6 +29,9 @@ class AdquiredDatasetsControllerAPI(base.BaseController):
log.info('Notification Request received')
if db.package_allowed_users_table is None:
db.init_db(model)
# Get the parser from the configuration
class_path = config.get(PARSER_CONFIG_PROP, '')
@ -55,23 +60,16 @@ class AdquiredDatasetsControllerAPI(base.BaseController):
for user_info in result['users_datasets']:
for dataset_id in user_info['datasets']:
try:
# Get dataset data
dataset = plugins.toolkit.get_action('package_show')({'ignore_auth': True}, {'id': dataset_id})
allowed_user = db.AllowedUser.get(package_id=dataset['id'], user_name=user_info['user'])
# Generate default set of users if it does not exist
if 'allowed_users' not in dataset or dataset['allowed_users'] is None:
dataset['allowed_users'] = ''
# Only new users will be inserted
allowed_users = dataset['allowed_users'].split(',')
if user_info['user'] not in allowed_users:
# Comma is only introduced when there are more than one user in the list of allowed users
separator = '' if dataset['allowed_users'] == '' else ','
dataset['allowed_users'] += separator + user_info['user']
# Update dataset data
plugins.toolkit.get_action('package_update')({'ignore_auth': True}, dataset)
if not allowed_user:
allowed_user = db.AllowedUser()
allowed_user.package_id = dataset['id']
allowed_user.user_name = user_info['user']
allowed_user.save()
model.Session.add(allowed_user)
else:
log.warn('The user %s is already allowed to access the %s dataset' % (user_info['user'], dataset_id))
@ -86,6 +84,9 @@ class AdquiredDatasetsControllerAPI(base.BaseController):
# but the process will continue
warns.append('Dataset %s: %s' % (dataset_id, e.error_dict['allowed_users'][0]))
# Commit the changes
model.Session.commit()
# Return warnings that inform about non-existing datasets
if len(warns) > 0:
return helpers.json.dumps({'warns': warns})

View File

@ -1,6 +1,7 @@
import ckan.lib.base as base
import ckan.model as model
import ckan.plugins as plugins
import ckanext.privatedatasets.db as db
import logging
from ckan.common import _
@ -12,6 +13,9 @@ class AdquiredDatasetsControllerUI(base.BaseController):
def user_adquired_datasets(self):
if db.package_allowed_users_table is None:
db.init_db(model)
c = plugins.toolkit.c
context = {
'model': model,
@ -29,13 +33,7 @@ class AdquiredDatasetsControllerUI(base.BaseController):
plugins.toolkit.abort(401, _('Not authorized to see this page'))
# Get the datasets adquired by the user
query = model.Session.query(model.PackageExtra).filter(
# Select only the allowed_users key
'package_extra.key=\'%s\' AND package_extra.value!=\'\' ' % 'allowed_users' +
# Select only when the state is 'active'
'AND package_extra.state=\'%s\' ' % 'active' +
# The user name should be contained in the list
'AND regexp_split_to_array(package_extra.value,\',\') @> ARRAY[\'%s\']' % context['user'])
query = db.AllowedUser.get(user_name=context['user'])
# Get the datasets
for dataset in query:

View File

@ -0,0 +1,43 @@
import db
from ckan.common import _
from itertools import count
def private_datasets_metadata_checker(key, data, errors, context):
# TODO: In some cases, we will need to retireve all the dataset information if it isn't present...
private_val = data.get(('private',))
private = private_val is True if isinstance(private_val, bool) else private_val == "True"
metadata_value = data[key]
# 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'))
def allowed_users_convert(key, data, errors, context):
if isinstance(data[key], basestring):
allowed_users = [allowed_user for allowed_user in data[key].split(',')]
else:
allowed_users = data[key]
current_index = max([int(k[1]) for k in data.keys() if len(k) == 2 and k[0] == 'allowed_users'] + [-1])
for num, allowed_user in zip(count(current_index + 1), allowed_users):
data[('allowed_users', num)] = allowed_user
def get_allowed_users(key, data, errors, context):
pkg_id = data[('id',)]
if db.package_allowed_users_table is None:
db.init_db(context['model'])
users = db.AllowedUser.get(package_id=pkg_id)
counter = 0
for user in users:
data[(key[0], counter)] = user.user_name
counter += 1

View File

@ -0,0 +1,43 @@
import sqlalchemy as sa
package_allowed_users_table = None
AllowedUser = None
def init_db(model):
class _AllowedUser(model.DomainObject):
@classmethod
def get(cls, **kw):
'''Finds all the instances required.'''
query = model.Session.query(cls).autoflush(False)
return query.filter_by(**kw).all()
global AllowedUser
AllowedUser = _AllowedUser
# We will just try to create the table. If it already exists we get an
# error but we can just skip it and carry on.
sql = '''
CREATE TABLE package_allowed_users (
package_id text NOT NULL,
user_name text NOT NULL
);
'''
conn = model.Session.connection()
try:
conn.execute(sql)
except sa.exc.ProgrammingError:
pass
model.Session.commit()
types = sa.types
global package_allowed_users_table
package_allowed_users_table = sa.Table('package_allowed_users', model.meta.metadata,
sa.Column('package_id', types.UnicodeText, primary_key=True, default=u''),
sa.Column('user_name', types.UnicodeText, primary_key=True, default=u''),
)
model.meta.mapper(
AllowedUser,
package_allowed_users_table,
)

View File

@ -1,3 +1,7 @@
.label-adquired {
background-color: #55a1ce;
}
.label-owner {
background-color: #e0051e;
}

View File

@ -0,0 +1,29 @@
import ckan.model as model
import ckan.plugins.toolkit as tk
import db
def is_adquired(pkg_dict):
adquired = False
if db.package_allowed_users_table is None:
db.init_db(model)
if db.AllowedUser.get(package_id=pkg_dict['id'], user_name=tk.c.user):
adquired = True
return adquired
def is_owner(pkg_dict):
owner = False
if tk.c.userobj.id == pkg_dict['creator_user_id']:
owner = True
return owner
def get_allowed_users_str(users):
return ','.join([user for user in users])

View File

@ -1,148 +1,9 @@
import ckan.lib.helpers as helpers
import ckan.logic.auth as logic_auth
import ckan.plugins as p
import ckan.plugins.toolkit as tk
import ckan.new_authz as new_authz
from ckan.common import _, request
######################################################################
########################### AUTH FUNCTIONS ###########################
######################################################################
@tk.auth_allow_anonymous_access
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:
return {'success': True}
# 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']
if allowed_users != '': # ''.split(',') ==> ['']
allowed_users_list = allowed_users.split(',')
if user in allowed_users_list:
authorized = True
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
if hasattr(package, 'extras') and 'adquire_url' in package.extras and request.path.startswith('/dataset/')\
and package.extras['adquire_url'] != '':
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}
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):
# TODO: In some cases, we will need to retireve all the dataset information if it isn't present...
private_val = data.get(('private',))
private = private_val is True if isinstance(private_val, bool) else private_val == "True"
metadata_value = data[key]
# 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'))
######################################################################
############################### ADQUIRED #############################
######################################################################
def adquired(pkg_dict):
adquired = False
if 'allowed_users' in pkg_dict and pkg_dict['allowed_users'] != '' and pkg_dict['allowed_users'] is not None:
allowed_users = pkg_dict['allowed_users'].split(',')
if tk.c.user in allowed_users:
adquired = True
return adquired
import auth
import converters_validators as conv_val
import db
import helpers as helpers
class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
@ -163,14 +24,16 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
# remove datasets_with_no_organization_cannot_be_private validator
'private': [tk.get_validator('ignore_missing'),
tk.get_validator('boolean_validator')],
'allowed_users_str': [tk.get_validator('ignore_missing'),
conv_val.allowed_users_convert,
conv_val.private_datasets_metadata_checker],
'allowed_users': [tk.get_validator('ignore_missing'),
private_datasets_metadata_checker,
tk.get_converter('convert_to_extras')],
conv_val.private_datasets_metadata_checker],
'adquire_url': [tk.get_validator('ignore_missing'),
private_datasets_metadata_checker,
conv_val.private_datasets_metadata_checker,
tk.get_converter('convert_to_extras')],
'searchable': [tk.get_validator('ignore_missing'),
private_datasets_metadata_checker,
conv_val.private_datasets_metadata_checker,
tk.get_converter('convert_to_extras'),
tk.get_validator('boolean_validator')]
}
@ -190,7 +53,7 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
def show_package_schema(self):
schema = super(PrivateDatasets, self).show_package_schema()
schema.update({
'allowed_users': [tk.get_converter('convert_from_extras'),
'allowed_users': [conv_val.get_allowed_users,
tk.get_validator('ignore_missing')],
'adquire_url': [tk.get_converter('convert_from_extras'),
tk.get_validator('ignore_missing')],
@ -214,9 +77,9 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
######################################################################
def get_auth_functions(self):
return {'package_show': package_show,
'package_update': package_update,
'resource_show': resource_show}
return {'package_show': auth.package_show,
'package_update': auth.package_update,
'resource_show': auth.resource_show}
######################################################################
############################ ICONFIGURER #############################
@ -281,9 +144,47 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
return pkg_dict
def after_update(self, context, pkg_dict):
session = context['session']
if db.package_allowed_users_table is None:
db.init_db(context['model'])
# Get the users and the package ID
received_users = [allowed_user for allowed_user in pkg_dict['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 received_users:
session.delete(user)
# Add non existing users
for user_name in received_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)
return pkg_dict
def after_show(self, context, pkg_dict):
user_obj = context.get('auth_user_obj')
# Only the package creator can update it
if not user_obj or (pkg_dict['creator_user_id'] != user_obj.id and not user_obj.sysadmin):
attrs = ['allowed_users', 'searchable', 'adquire_url']
for attr in attrs:
if attr in pkg_dict:
del pkg_dict[attr]
return pkg_dict
def after_search(self, search_results, search_params):
@ -297,4 +198,6 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
######################################################################
def get_helpers(self):
return {'privatedatasets_adquired': adquired}
return {'privatedatasets_adquired': helpers.is_adquired,
'get_allowed_users_str': helpers.get_allowed_users_str,
'is_owner': helpers.is_owner}

View File

@ -83,7 +83,7 @@
{% endif %}
{% set users_attrs = {'data-module': 'autocomplete', 'data-module-tags': '', 'data-module-source': '/api/2/util/user/autocomplete?q=?'} %}
{{ form.input('allowed_users', label=_('Allowed Users'), id='field-allowed_users', placeholder=_('Allowed Users'), value=data.allowed_users, error=errors.custom_text, classes=['control-full'], attrs=users_attrs) }}
{{ form.input('allowed_users_str', label=_('Allowed Users'), id='field-allowed_users_str', placeholder=_('Allowed Users'), value=h.get_allowed_users_str(data.allowed_users), error=errors.custom_text, classes=['control-full'], attrs=users_attrs) }}
{{ form.input('adquire_url', label=_('Adquire URL'), id='field-adquire_url', placeholder=_('http://example.com/adquire/'), value=data.adquire_url, error=errors.custom_text, classes=['control-medium']) }}

View File

@ -18,6 +18,7 @@ Example:
{% set title = package.title or package.name %}
{% set notes = h.markdown_extract(package.notes, extract_length=truncate) %}
{% set adquired = h.privatedatasets_adquired(package) %}
{% set owner = h.is_owner(package) %}
{% resource 'privatedatasets/custom.css' %}
@ -25,7 +26,7 @@ Example:
{% block package_item_content %}
<div class="dataset-content">
<h3 class="dataset-heading">
{% if package.private and not adquired %}
{% if package.private and not adquired and not owner %}
<span class="dataset-private label label-inverse">
<i class="icon-lock"></i>
{{ _('Private') }}
@ -37,6 +38,12 @@ Example:
{{ _('Adquired') }}
</span>
{% endif %}
{% if owner and package.private %}
<span class="dataset-private label label-owner">
<i class="icon-user"></i>
{{ _('Owner') }}
</span>
{% endif %}
{{ h.link_to(h.truncate(title, truncate_title), h.url_for(controller='package', action='read', id=package.name)) }}
{% if package.get('state', '').startswith('draft') %}
<span class="label label-info">{{ _('Draft') }}</span>