Compare commits
101 Commits
Author | SHA1 | Date |
---|---|---|
Francesco Mangiacrapa | f2c13b162e | |
Francesco Mangiacrapa | 74aee9bd48 | |
Francesco Mangiacrapa | db7e30259e | |
Francisco de la Vega | 9f3929344a | |
Francisco de la Vega | da521ebad8 | |
SSladarov | 5341906767 | |
Francisco de la Vega | bdb1e3ff57 | |
Francisco de la Vega | a6c9541bbb | |
Álvaro Arranz | 520da2652c | |
Francisco de la Vega | cc776c68d9 | |
Álvaro Arranz | 2b8ef43dee | |
Álvaro Arranz | d98a5e4831 | |
Álvaro Arranz | 1ab4484f66 | |
Álvaro Arranz | 6edcb98629 | |
Álvaro Arranz | a6eb52bb9d | |
Álvaro Arranz | 2430c0d46a | |
Álvaro Arranz | c8bba3c15f | |
Álvaro Arranz | 56401296d3 | |
Álvaro Arranz | 9d86939989 | |
Álvaro Arranz | 730f5c7b6e | |
Álvaro Arranz | f1341686c0 | |
Álvaro Arranz | c43c549539 | |
Álvaro Arranz | 5e2920f31c | |
Álvaro Arranz | fd5c692652 | |
Álvaro Arranz | bfb9d6d62a | |
Álvaro Arranz | bafdd6a707 | |
Álvaro Arranz | 1598723d4c | |
Álvaro Arranz | 04144863fa | |
Álvaro Arranz | ce4ec44f1b | |
Álvaro Arranz | 1030939052 | |
Álvaro Arranz | 68dc704d6b | |
Álvaro Arranz | 73e4049c15 | |
Álvaro Arranz | b0e30ae0f9 | |
Álvaro Arranz | 8d0a086159 | |
Álvaro Arranz | d1b8eb2662 | |
Álvaro Arranz | c5f0efe809 | |
psonis | 2263bdb17f | |
Álvaro Arranz | a24880adc8 | |
Francisco de la Vega | 1f9d13f3e3 | |
Francisco de la Vega | 342a42b4d9 | |
Francisco de la Vega | 958d621ff4 | |
Francisco de la Vega | af8283794c | |
Francisco de la Vega | acaea8782c | |
Francisco de la Vega | f24ec2b428 | |
Francisco de la Vega | 1682108037 | |
Francisco de la Vega | a7f7069975 | |
Francisco de la Vega | fd7f99a438 | |
Francisco de la Vega | 76b5bb82d9 | |
Francisco de la Vega | 19894497b2 | |
Francisco de la Vega | 65d208421f | |
Francisco de la Vega | 2cba3fbe0f | |
Álvaro Arranz | 62c8fa4f8f | |
Francisco de la Vega | c7eef4df32 | |
Álvaro Arranz | efae19f0fb | |
Álvaro Arranz | 77f4e50f65 | |
fdelavega | 8d8c28dd85 | |
fdelavega | 018b84127c | |
Francisco de la Vega | 6fc906a003 | |
egonzalo | 3673353891 | |
egonzalo | 6bbd9f5b95 | |
egonzalo | 1bb294832e | |
Aitor Magán García | a1490bf88f | |
Aitor Magán García | d1f92cba6e | |
Aitor Magán García | 596464e41e | |
Aitor Magán García | b3aeafda5e | |
Aitor Magán García | 06e09083b0 | |
Aitor Magán García | ad26e11963 | |
aitor.magan | 83f81d56ea | |
Aitor Magán García | 00e8a69ec2 | |
Aitor Magán | f7ee7e325f | |
Aitor Magán | 7e9305db29 | |
Aitor Magán | 843ad8a264 | |
aitor.magan | e77e91045e | |
Cazaril | 014779a096 | |
Eugenio Gonzalo | 5b6bd59715 | |
Cazaril | 0eca66d015 | |
Rômulo Barroso Victor | 4cf0e9a70a | |
Aitor Magán García | 5a483b798e | |
Aitor Magán | a1d9516faf | |
Aitor Magán | e98c44bdb7 | |
Aitor Magán | c47949155e | |
Aitor Magán | 273f902d76 | |
Aitor Magán García | 57e486c5e8 | |
Aitor Magán | 4d920b1abc | |
Aitor Magán | 3116417ed1 | |
Aitor Magán | af7468ee6b | |
Aitor Magán García | 07ff283ec6 | |
Aitor Magán | 07d22a8733 | |
Aitor Magán | e0975bb820 | |
Aitor Magán | 66118bb712 | |
Aitor Magán | 5454b614b4 | |
Aitor Magán García | e33a14c1d9 | |
Aitor Magán | 4071213a55 | |
Aitor Magán | 19f929d60b | |
Aitor Magán García | 47e65279bd | |
Aitor Magán García | 172bfa221f | |
Aitor Magán García | 98923e431b | |
Aitor Magán | 3362eec822 | |
Aitor Magán | 3359252c6a | |
Aitor Magán | 4eb13dcc65 | |
Aitor Magán | 844cad96a8 |
|
@ -20,7 +20,7 @@ var/
|
|||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
.eggs
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
@ -50,3 +50,6 @@ coverage.xml
|
|||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
.idea
|
||||
venv
|
|
@ -0,0 +1,30 @@
|
|||
sudo: required
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
env:
|
||||
- CKANVERSION=2.7.3
|
||||
- CKANVERSION=2.8.1
|
||||
- CKANVERSION=2.8.2
|
||||
services:
|
||||
- redis-server
|
||||
- postgresql
|
||||
- xvfb
|
||||
addons:
|
||||
firefox: "60.1.0esr"
|
||||
before_install:
|
||||
- wget https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-linux64.tar.gz
|
||||
- mkdir geckodriver
|
||||
- tar -xzf geckodriver-v0.21.0-linux64.tar.gz -C geckodriver
|
||||
- export PATH=$PATH:$PWD/geckodriver
|
||||
install:
|
||||
- bash bin/travis-build.bash
|
||||
before_script:
|
||||
- "export DISPLAY=:99.0"
|
||||
- sleep 3 # give xvfb some time to start
|
||||
script:
|
||||
- sh bin/travis-run.sh
|
||||
after_success: coveralls
|
||||
branches:
|
||||
only:
|
||||
- master
|
|
@ -0,0 +1,3 @@
|
|||
recursive-include ckanext/privatedatasets/templates *
|
||||
recursive-include ckanext/privatedatasets/templates_2.8 *
|
||||
recursive-include ckanext/privatedatasets/fanstatic *
|
63
README.md
63
README.md
|
@ -1,40 +1,60 @@
|
|||
CKAN Private Datasets [![Build Status](https://build.conwet.fi.upm.es/jenkins/buildStatus/icon?job=ckan_privatedatasets-develop)](https://build.conwet.fi.upm.es/jenkins/job/ckan_privatedatasets-develop/)
|
||||
D4Science CKAN Private Datasets [![Build Status](https://travis-ci.org/conwetlab/ckanext-privatedatasets.svg?branch=master)](https://travis-ci.org/conwetlab/ckanext-privatedatasets) [![Coverage Status](https://coveralls.io/repos/github/conwetlab/ckanext-privatedatasets/badge.svg?branch=master)](https://coveralls.io/github/conwetlab/ckanext-privatedatasets?branch=develop)
|
||||
=====================
|
||||
|
||||
This CKAN extension allows a user to create private datasets that only certain users will be able to see. When a dataset is being created, it's possible to specify the list of users that can see this dataset. In addition, the extension provides an HTTP API that allows to add users programatically.
|
||||
This CKAN extension allows a user to create private datasets that only certain users will be able to see. When a dataset is being created, it's possible to specify the list of users that can see this dataset. In addition, the extension provides an HTTP API that allows to add users programmatically.
|
||||
|
||||
This project is part of [FIWARE](http://www.fiware.org).
|
||||
|
||||
Installation
|
||||
------------
|
||||
Install this extension in your CKAN instance is as easy as intall any other CKAN extension.
|
||||
Install this extension in your CKAN instance is as easy as install any other CKAN extension.
|
||||
|
||||
* Download the source from this GitHub repo.
|
||||
* Activate your virtual environment (generally by running `. /usr/lib/ckan/default/bin/activate`)
|
||||
* Install the extension by running `python setup.py develop`
|
||||
* Modify your configuration file (generally in `/etc/ckan/default/production.ini`) and add `privatedatasets` in the `ckan.plugins` setting.
|
||||
* Activate your virtual environment
|
||||
```
|
||||
. /usr/lib/ckan/default/bin/activate
|
||||
```
|
||||
* Install the extension by running
|
||||
```
|
||||
pip install ckanext-privatedatasets
|
||||
```
|
||||
> **Note**: If you prefer, you can also download the source code and install the extension manually. To do so, execute the following commands:
|
||||
> ```
|
||||
> $ git clone https://github.com/conwetlab/ckanext-privatedatasets.git
|
||||
> $ cd ckanext-privatedatasets
|
||||
> $ python setup.py install
|
||||
> ```
|
||||
|
||||
* Modify your configuration file (generally in `/etc/ckan/default/production.ini`) and add `privatedatasets` in the `ckan.plugins` property.
|
||||
```
|
||||
ckan.plugins = privatedatasets <OTHER_PLUGINS>
|
||||
```
|
||||
* In the same config file, specify the location of your parser by adding the `ckan.privatedatasets.parser` setting. For example, to set the [FiWareNotificationParser](https://github.com/conwetlab/ckanext-privatedatasets/blob/master/ckanext/privatedatasets/parsers/fiware.py) as notification parser, add the following line: `ckan.privatedatasets.parser = ckanext.privatedatasets.parsers.fiware:FiWareNotificationParser`.
|
||||
* If you want you can also add some preferences to set if the Acquire URL should be shown when the user is to create and/or editing a dataset:
|
||||
* To show the Acquire URL when the user is **creating** a dataset, you should set the following preference: `ckan.privatedatasets.show_acquire_url_on_create = True`. By default, the value of this preference is set to `False`.
|
||||
* To show the Acquire URL when the user is **editing** a dataset, you should set the following preference: `ckan.privatedatasets.show_acquire_url_on_edit = True`. By default, the value of this preference is set to `False`.
|
||||
* In some cases you will want to secure the notification callback in order to filter the entities (user, machines...) that can send them. To do so, you can follow the instructions in the section [Securing the Notification Callback](#securing-the-notification-callback).
|
||||
* Restart your apache2 reserver (`sudo service apache2 restart`)
|
||||
* In some cases you will want to secure the notification callback in order to filter the entities (user, machines...) that can send them. To do so, you can follow the instructions in the section [Securing the Notification Callback](#securing-the-notification-callback).
|
||||
* Restart your apache2 server
|
||||
```
|
||||
sudo service apache2 restart
|
||||
```
|
||||
* That's All!
|
||||
|
||||
Creating a notification parser
|
||||
------------------------------
|
||||
Since each service can send notifications in a different way, the extension allows developers to create their own notifications parser. As default, we provide you a basic parser based on the notifications sent by the [FiWare Store](https://github.com/conwetlab/wstore/).
|
||||
Since each service can send notifications in a different way, the extension allows developers to create their own notifications parser. As default, we provide you a basic parser based on the notifications sent by the [FiWare Store](https://github.com/conwetlab/wstore/).
|
||||
|
||||
If you want to create your own parser, you have to:
|
||||
|
||||
1. Create a class with a method called `parse_notification`. This method will recieve one argument that will include the notification body.
|
||||
1. Create a class with a method called `parse_notification`. This method will receive one argument that will include the notification body.
|
||||
2. Parse the notification as you like. You can raise a CKAN's default exception (`ValidationError`, `ObjectNotFound`, `NotAuthorized`, `ValidationError`, `SearchError`, `SearchQueryError` or `SearchIndexError`) if you find an error parsing the notification.
|
||||
3. Return a dictionary with the structure attached below. The `users_datasets` is the lists of datasets available for each user (each element of this list is a dictionary with two fields: `user` and `datasets`).
|
||||
3. Return a dictionary with the structure attached below. The `users_datasets` is the lists of datasets available for each user (each element of this list is a dictionary with two fields: `user` and `datasets`).
|
||||
|
||||
```
|
||||
{'users_datasets': [{'user': 'user_name', 'datasets': ['ds1', 'ds2', ...]},
|
||||
{'user': 'user_name2', 'datasets': ['ds1', 'ds4', ...] }]}
|
||||
```
|
||||
|
||||
Finally, you have to modify your config file and specify in the `ckan.privatedatasets.parser` the location of your own parser.
|
||||
Finally, you have to modify your config file and specify in the `ckan.privatedatasets.parser` the location of your own parser.
|
||||
|
||||
At this point, you will be able to add users via API by accessing the following URL:
|
||||
|
||||
|
@ -89,13 +109,13 @@ crlDistributionPoints = @crl
|
|||
URI=http://testca.local/ca.crl
|
||||
```
|
||||
|
||||
Then, you should create your CA using the previous config file. To do so, you can execure the following line (replace `<PATH_TO_SSL_CONFIG_FILE>` by the real path of your OpenSSL config file):
|
||||
Then, you should create your CA using the previous config file. To do so, you can execute the following line (replace `<PATH_TO_SSL_CONFIG_FILE>` by the real path of your OpenSSL config file):
|
||||
|
||||
```
|
||||
$ openssl req -config <PATH_TO_SSL_CONFIG_FILE> -newkey rsa:2048 -nodes -keyform PEM -keyout ca.key -x509 -days 3650 -extensions certauth -outform PEM -out ca.cer
|
||||
```
|
||||
|
||||
Afterwards, you will need to filter the notification callback to be callable only by those entities that use a valid certificate (the one signed by the CA created previously). To achieve this, edit the file `/etc/apache2/sites-available/ckan_default` and add the following lines inmediatly after the SSL configuration (replace `<PATH_TO_SSL_CONFIG_FILE>` by the real path of your OpenSSL config file):
|
||||
Afterwards, you will need to filter the notification callback to be callable only by those entities that use a valid certificate (the one signed by the CA created previously). To achieve this, edit the file `/etc/apache2/sites-available/ckan_default` and add the following lines immediately after the SSL configuration (replace `<PATH_TO_SSL_CONFIG_FILE>` by the real path of your OpenSSL config file):
|
||||
|
||||
```
|
||||
<Location /api/action/dataset_acquired>
|
||||
|
@ -119,17 +139,12 @@ $ openssl x509 -req -in client.req -CA ca.cer -CAkey ca.key -set_serial 101 -ext
|
|||
$ openssl pkcs12 -export -inkey client.key -in client.cer -out client.p12
|
||||
```
|
||||
|
||||
That's all! You notification callback is completly secure now! Enjoy it :)
|
||||
That's all! You notification callback is completely secure now! Enjoy it :)
|
||||
|
||||
Tests
|
||||
-----
|
||||
This sofware contains a set of test to detect errors and failures. You can run this tests by running the following command:
|
||||
This sofware contains a set of test to detect errors and failures. You can run this tests by running the following command (this command will generate coverage reports):
|
||||
```
|
||||
nosetests --ckan --with-pylons=test.ini ckanext/privatedatasets/tests/
|
||||
```
|
||||
**Note:** The `test.ini` file contains a link to the CKAN `test-core.ini` file. You will need to change that link to the real path of the file in your system (generally `/usr/lib/ckan/default/src/ckan/test-core.ini`).
|
||||
|
||||
You can also generate coverage reports by running:
|
||||
```
|
||||
nosetests --ckan --with-xunit --with-pylons=test.ini ckanext/privatedatasets/tests/ --with-coverage --cover-package=ckanext.privatedatasets --cover-inclusive --cover-erase . --cover-xml
|
||||
python setup.py nosetests
|
||||
```
|
||||
**Note:** The `test.ini` file contains a link to the CKAN `test-core.ini` file. You will need to change that link to the real path of the file in your system (generally `/usr/lib/ckan/default/src/ckan/test-core.ini`).
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -xe
|
||||
trap 'jobs -p | xargs --no-run-if-empty kill' INT TERM EXIT
|
||||
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
export PIP_DOWNLOAD_CACHE=~/.pip_cache
|
||||
|
||||
WD=`pwd`
|
||||
POSTGRES_PORT=${POSTGRES_PORT:=5432}
|
||||
CACHE_DIR=~/.cache
|
||||
|
||||
|
||||
echo "Downloading CKAN..."
|
||||
git clone https://github.com/ckan/ckan
|
||||
cd ckan
|
||||
git checkout release-v2.2.2
|
||||
cd $WD
|
||||
|
||||
|
||||
echo "Checking Solr..."
|
||||
SOLR_ACTIVE=`nc -z localhost 8983; echo $?`
|
||||
|
||||
if [ $SOLR_ACTIVE -ne 0 ]
|
||||
then
|
||||
|
||||
echo "Downloading Solr..."
|
||||
CACHE_DIR=~/.cache
|
||||
FILE=solr-4.8.1.tgz
|
||||
SOLAR_UNZIP_FOLDER=solr-4.8.1
|
||||
|
||||
# If the solar folder does not exist, we have to build it
|
||||
if [ ! -d "$CACHE_DIR/$SOLAR_UNZIP_FOLDER" ]
|
||||
then
|
||||
# Download the solar installation file if it does not exist
|
||||
wget --quiet --timestamping --directory-prefix=$CACHE_DIR http://apache.rediris.es/lucene/solr/4.8.1/$FILE
|
||||
|
||||
# Unzip the folder
|
||||
tar -xf "$CACHE_DIR/$FILE" --directory "$CACHE_DIR"
|
||||
|
||||
# Delete the downloaded tar.gz
|
||||
rm "$CACHE_DIR/$FILE"
|
||||
fi
|
||||
|
||||
echo "Configuring and starting Solr..."
|
||||
ln -s "$CACHE_DIR/$SOLAR_UNZIP_FOLDER" .
|
||||
mv "$SOLAR_UNZIP_FOLDER/example/solr/collection1/conf/schema.xml" "$SOLAR_UNZIP_FOLDER/example/solr/collection1/conf/schema.xml.bak"
|
||||
ln -s $WD/ckan/ckan/config/solr/schema.xml "$SOLAR_UNZIP_FOLDER/example/solr/collection1/conf/schema.xml"
|
||||
cd solr-4.8.1/example
|
||||
java -jar start.jar 2>&1 > /dev/null &
|
||||
cd $WD
|
||||
|
||||
else
|
||||
echo "Solar is already installed..."
|
||||
fi
|
||||
|
||||
|
||||
echo "Setting up virtualenv..."
|
||||
virtualenv --no-site-packages virtualenv
|
||||
source virtualenv/bin/activate
|
||||
pip install --upgrade pip
|
||||
|
||||
|
||||
echo "Installing CKAN dependencies..."
|
||||
cd ckan
|
||||
python setup.py develop
|
||||
pip install -r requirements.txt --allow-all-external
|
||||
pip install -r dev-requirements.txt --allow-all-external
|
||||
cd ..
|
||||
|
||||
|
||||
echo "Removing databases from old executions..."
|
||||
sudo -u postgres psql -c "DROP DATABASE IF EXISTS datastore_test;"
|
||||
sudo -u postgres psql -c "DROP DATABASE IF EXISTS ckan_test;"
|
||||
sudo -u postgres psql -c "DROP USER IF EXISTS ckan_default;"
|
||||
|
||||
|
||||
echo "Creating the PostgreSQL user and database..."
|
||||
sudo -u postgres psql -c "CREATE USER ckan_default WITH PASSWORD 'pass';"
|
||||
sudo -u postgres psql -c 'CREATE DATABASE ckan_test WITH OWNER ckan_default;'
|
||||
sudo -u postgres psql -c 'CREATE DATABASE datastore_test WITH OWNER ckan_default;'
|
||||
|
||||
|
||||
echo "Modifying the configuration to setup properly the Postgres port..."
|
||||
mkdir -p data/storage
|
||||
echo "
|
||||
sqlalchemy.url = postgresql://ckan_default:pass@localhost:$POSTGRES_PORT/ckan_test
|
||||
ckan.datastore.write_url = postgresql://ckan_default:pass@localhost:$POSTGRES_PORT/datastore_test
|
||||
ckan.datastore.read_url = postgresql://datastore_default:pass@localhost:$POSTGRES_PORT/datastore_test
|
||||
|
||||
ckan.storage_path=data/storage" >> test.ini
|
||||
|
||||
|
||||
echo "Initializing the database..."
|
||||
sed -i "s/\(postgresql:\/\/.\+@localhost\)/\1:$POSTGRES_PORT/g" ckan/test-core.ini
|
||||
cd ckan
|
||||
paster db init -c test-core.ini
|
||||
cd ..
|
||||
|
||||
|
||||
echo "Installing ckanext-privatedatasets and its requirements..."
|
||||
python setup.py develop
|
||||
pip install -r dev-requirements.txt
|
||||
|
||||
|
||||
echo "Running tests..."
|
||||
nosetests --ckan --with-xunit --with-pylons=test.ini ckanext/privatedatasets/tests/ --with-coverage \
|
||||
--cover-package=ckanext.privatedatasets --cover-inclusive --cover-erase . --cover-xml
|
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "This is travis-build.bash..."
|
||||
|
||||
echo "Installing the packages that CKAN requires..."
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install solr-jetty
|
||||
|
||||
echo "Installing CKAN and its Python dependencies..."
|
||||
git clone https://github.com/ckan/ckan
|
||||
cd ckan
|
||||
git checkout ckan-$CKANVERSION
|
||||
python setup.py develop
|
||||
|
||||
sed -i "s|psycopg2==2.4.5|psycopg2==2.7.1|g" requirements.txt
|
||||
|
||||
pip install -r requirements.txt
|
||||
pip install -r dev-requirements.txt
|
||||
cd -
|
||||
|
||||
echo "Checking solr"
|
||||
ls -la /etc/
|
||||
|
||||
echo "Setting up Solr..."
|
||||
# solr is multicore for tests on ckan master now, but it's easier to run tests
|
||||
# on Travis single-core still.
|
||||
# see https://github.com/ckan/ckan/issues/2972
|
||||
sed -i -e 's/solr_url.*/solr_url = http:\/\/127.0.0.1:8080\/solr/' ckan/test-core.ini
|
||||
printf "NO_START=0\nJETTY_HOST=127.0.0.1\nJETTY_PORT=8080\nJAVA_HOME=$JAVA_HOME" | sudo tee /etc/default/jetty
|
||||
sudo cp ckan/ckan/config/solr/schema.xml /etc/solr/conf/schema.xml
|
||||
sudo service jetty8 restart
|
||||
|
||||
echo "Creating the PostgreSQL user and database..."
|
||||
sudo -u postgres psql -c "CREATE USER ckan_default WITH PASSWORD 'pass';"
|
||||
sudo -u postgres psql -c "CREATE USER datastore_default WITH PASSWORD 'pass';"
|
||||
sudo -u postgres psql -c "CREATE DATABASE ckan_test WITH OWNER ckan_default;"
|
||||
sudo -u postgres psql -c "CREATE DATABASE datastore_test WITH OWNER ckan_default;"
|
||||
|
||||
|
||||
echo "Initialising the database..."
|
||||
cd ckan
|
||||
paster db init -c test-core.ini
|
||||
cd -
|
||||
|
||||
echo "Installing ckanext-privatedatasets and its requirements..."
|
||||
python setup.py develop
|
||||
|
||||
echo "travis-build.bash is done."
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
echo "Starting Jetty"
|
||||
sudo service jetty8 restart
|
||||
|
||||
sudo netstat -ntlp
|
||||
|
||||
python setup.py nosetests
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||
# 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.
|
||||
|
||||
|
@ -17,12 +18,16 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with CKAN Private Dataset Extension. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ckan.plugins as plugins
|
||||
import ckanext.privatedatasets.constants as constants
|
||||
import db
|
||||
from __future__ import absolute_import
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
import pylons.config as config
|
||||
import os
|
||||
|
||||
import ckan.plugins as plugins
|
||||
|
||||
from ckanext.privatedatasets import constants, db
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -52,92 +57,9 @@ def package_acquired(context, request_data):
|
|||
:rtype: dict
|
||||
|
||||
'''
|
||||
context['method'] = 'grant'
|
||||
return _process_package(context, request_data)
|
||||
|
||||
log.info('Notification received: %s' % request_data)
|
||||
|
||||
# Check access
|
||||
plugins.toolkit.check_access(constants.PACKAGE_ACQUIRED, context, request_data)
|
||||
|
||||
# Get the parser from the configuration
|
||||
class_path = config.get(PARSER_CONFIG_PROP, '')
|
||||
|
||||
if class_path != '':
|
||||
try:
|
||||
cls = class_path.split(':')
|
||||
class_package = cls[0]
|
||||
class_name = cls[1]
|
||||
parser_cls = getattr(importlib.import_module(class_package), class_name)
|
||||
parser = parser_cls()
|
||||
except Exception as e:
|
||||
raise plugins.toolkit.ValidationError({'message': '%s: %s' % (type(e).__name__, str(e))})
|
||||
else:
|
||||
raise plugins.toolkit.ValidationError({'message': '%s not configured' % PARSER_CONFIG_PROP})
|
||||
|
||||
# Parse the result using the parser set in the configuration
|
||||
# Expected result: {'errors': ["...", "...", ...]
|
||||
# 'users_datasets': [{'user': 'user_name', 'datasets': ['ds1', 'ds2', ...]}, ...]}
|
||||
result = parser.parse_notification(request_data)
|
||||
|
||||
warns = []
|
||||
|
||||
# Introduce the users into the datasets
|
||||
for user_info in result['users_datasets']:
|
||||
for dataset_id in user_info['datasets']:
|
||||
try:
|
||||
|
||||
context_pkg_show = context.copy()
|
||||
context_pkg_show['ignore_auth'] = True
|
||||
context_pkg_show[constants.CONTEXT_CALLBACK] = True
|
||||
dataset = plugins.toolkit.get_action('package_show')(context_pkg_show, {'id': dataset_id})
|
||||
|
||||
# This operation can only be performed with private datasets
|
||||
# This check is redundant since the package_update function will throw an exception
|
||||
# if a list of allowed users is included in a public dataset. However, this check
|
||||
# should be performed in order to avoid strange future exceptions
|
||||
if dataset.get('private', None) is True:
|
||||
|
||||
# Create the array if it does not exist
|
||||
if constants.ALLOWED_USERS not in dataset or dataset[constants.ALLOWED_USERS] is None:
|
||||
dataset[constants.ALLOWED_USERS] = []
|
||||
|
||||
# Add the user only if it is not in the list
|
||||
if user_info['user'] not in dataset[constants.ALLOWED_USERS]:
|
||||
dataset[constants.ALLOWED_USERS].append(user_info['user'])
|
||||
context_pkg_update = context.copy()
|
||||
context_pkg_update['ignore_auth'] = True
|
||||
|
||||
# Set creator as the user who is performing the changes
|
||||
user_show = plugins.toolkit.get_action('user_show')
|
||||
creator_user_id = dataset.get('creator_user_id', '')
|
||||
user_show_context = {'ignore_auth': True}
|
||||
user = user_show(user_show_context, {'id': creator_user_id})
|
||||
context_pkg_update['user'] = user.get('name', '')
|
||||
|
||||
plugins.toolkit.get_action('package_update')(context_pkg_update, dataset)
|
||||
log.info('Allowed Users added correctly')
|
||||
else:
|
||||
log.warn('The user %s is already allowed to access the %s dataset' % (user_info['user'], dataset_id))
|
||||
else:
|
||||
log.warn('Dataset %s is public. Allowed Users cannot be added')
|
||||
warns.append('Unable to upload the dataset %s: It\'s a public dataset' % dataset_id)
|
||||
|
||||
except plugins.toolkit.ObjectNotFound:
|
||||
# If a dataset does not exist in the instance, an error message will be returned to the user.
|
||||
# However the process won't stop and the process will continue with the remaining datasets.
|
||||
log.warn('Dataset %s was not found in this instance' % dataset_id)
|
||||
warns.append('Dataset %s was not found in this instance' % dataset_id)
|
||||
except plugins.toolkit.ValidationError as e:
|
||||
# Some datasets does not allow to introduce the list of allowed users since this property is
|
||||
# only valid for private datasets outside an organization. In this case, a wanr will return
|
||||
# but the process will continue
|
||||
# WARN: This exception should not be risen anymore since public datasets are not updated.
|
||||
message = '%s(%s): %s' % (dataset_id, constants.ALLOWED_USERS, e.error_dict[constants.ALLOWED_USERS][0])
|
||||
log.warn(message)
|
||||
warns.append(message)
|
||||
|
||||
# Return warnings that inform about non-existing datasets
|
||||
if len(warns) > 0:
|
||||
return {'warns': warns}
|
||||
|
||||
def acquisitions_list(context, data_dict):
|
||||
'''
|
||||
|
@ -178,7 +100,7 @@ def acquisitions_list(context, data_dict):
|
|||
# Get the datasets
|
||||
for dataset in query:
|
||||
try:
|
||||
dataset_show_func = 'dataset_show'
|
||||
dataset_show_func = 'package_show'
|
||||
func_data_dict = {'id': dataset.package_id}
|
||||
internal_context = context.copy()
|
||||
|
||||
|
@ -194,3 +116,124 @@ def acquisitions_list(context, data_dict):
|
|||
pass
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def revoke_access(context, request_data):
|
||||
'''
|
||||
API action to be called in order to revoke access grants of an user.
|
||||
|
||||
This API should be called to delete the user from the list of allowed users.
|
||||
|
||||
Since each service can provide a different way of pushing the data, the received
|
||||
data will be forwarded to the parser set in the preferences. This parser should
|
||||
return a dict similar to the following one:
|
||||
{'errors': ["...", "...", ...]
|
||||
'users_datasets': [{'user': 'user_name', 'datasets': ['ds1', 'ds2', ...]}, ...]}
|
||||
1) 'errors' contains the list of errors. It should be empty if no errors arised
|
||||
while the notification is parsed
|
||||
2) 'users_datasets' is the lists of datasets available for each user (each element
|
||||
of this list is a dictionary with two fields: user and datasets).
|
||||
|
||||
:parameter request_data: Depends on the parser
|
||||
:type request_data: dict
|
||||
|
||||
:return: A list of warnings or None if the list of warnings is empty
|
||||
:rtype: dict
|
||||
|
||||
'''
|
||||
context['method'] = 'revoke'
|
||||
return _process_package(context, request_data)
|
||||
|
||||
|
||||
def _process_package(context, request_data):
|
||||
log.info('Notification received: %s' % request_data)
|
||||
|
||||
# Check access
|
||||
method = constants.PACKAGE_ACQUIRED if context.get('method') == 'grant' else constants.PACKAGE_DELETED
|
||||
plugins.toolkit.check_access(method, context, request_data)
|
||||
|
||||
# Get the parser from the configuration
|
||||
class_path = os.environ.get(PARSER_CONFIG_PROP.upper().replace('.', '_'), plugins.toolkit.config.get(PARSER_CONFIG_PROP, ''))
|
||||
|
||||
if class_path != '':
|
||||
try:
|
||||
cls = class_path.split(':')
|
||||
class_package = cls[0]
|
||||
class_name = cls[1]
|
||||
parser_cls = getattr(importlib.import_module(class_package), class_name)
|
||||
parser = parser_cls()
|
||||
except Exception as e:
|
||||
raise plugins.toolkit.ValidationError({'message': '%s: %s' % (type(e).__name__, str(e))})
|
||||
else:
|
||||
raise plugins.toolkit.ValidationError({'message': '%s not configured' % PARSER_CONFIG_PROP})
|
||||
|
||||
# Parse the result using the parser set in the configuration
|
||||
# Expected result: {'errors': ["...", "...", ...]
|
||||
# 'users_datasets': [{'user': 'user_name', 'datasets': ['ds1', 'ds2', ...]}, ...]}
|
||||
result = parser.parse_notification(request_data)
|
||||
|
||||
warns = []
|
||||
|
||||
for user_info in result['users_datasets']:
|
||||
for dataset_id in user_info['datasets']:
|
||||
|
||||
try:
|
||||
context_pkg_show = context.copy()
|
||||
context_pkg_show['ignore_auth'] = True
|
||||
context_pkg_show[constants.CONTEXT_CALLBACK] = True
|
||||
dataset = plugins.toolkit.get_action('package_show')(context_pkg_show, {'id': dataset_id})
|
||||
|
||||
# This operation can only be performed with private datasets
|
||||
# This check is redundant since the package_update function will throw an exception
|
||||
# if a list of allowed users is included in a public dataset. However, this check
|
||||
# should be performed in order to avoid strange future exceptions
|
||||
if dataset.get('private', None) is True:
|
||||
|
||||
# Create the array if it does not exist
|
||||
if constants.ALLOWED_USERS not in dataset or dataset[constants.ALLOWED_USERS] is None:
|
||||
dataset[constants.ALLOWED_USERS] = []
|
||||
|
||||
method = context['method'] == 'grant'
|
||||
present = user_info['user'] in dataset[constants.ALLOWED_USERS]
|
||||
# Deletes the user only if it is in the list
|
||||
if (not method and present) or (method and not present):
|
||||
if method:
|
||||
dataset[constants.ALLOWED_USERS].append(user_info['user'])
|
||||
else:
|
||||
dataset[constants.ALLOWED_USERS].remove(user_info['user'])
|
||||
|
||||
context_pkg_update = context.copy()
|
||||
context_pkg_update['ignore_auth'] = True
|
||||
|
||||
# Set creator as the user who is performing the changes
|
||||
user_show = plugins.toolkit.get_action('user_show')
|
||||
creator_user_id = dataset.get('creator_user_id', '')
|
||||
user_show_context = {'ignore_auth': True}
|
||||
user = user_show(user_show_context, {'id': creator_user_id})
|
||||
context_pkg_update['user'] = user.get('name', '')
|
||||
|
||||
plugins.toolkit.get_action('package_update')(context_pkg_update, dataset)
|
||||
log.info('Action %s access to dataset ended successfully' % context['method'])
|
||||
else:
|
||||
log.warn('Action %s access to dataset not completed. The dataset %s already %s access to the user %s' % (context['method'], dataset_id, context['method'], user_info['user']))
|
||||
else:
|
||||
log.warn('Dataset %s is public. Cannot %s access to users' % (dataset_id, context['method']))
|
||||
warns.append('Unable to upload the dataset %s: It\'s a public dataset' % dataset_id)
|
||||
|
||||
except plugins.toolkit.ObjectNotFound:
|
||||
# If a dataset does not exist in the instance, an error message will be returned to the user.
|
||||
# However the process won't stop and the process will continue with the remaining datasets.
|
||||
log.warn('Dataset %s was not found in this instance' % dataset_id)
|
||||
warns.append('Dataset %s was not found in this instance' % dataset_id)
|
||||
except plugins.toolkit.ValidationError as e:
|
||||
# Some datasets does not allow to introduce the list of allowed users since this property is
|
||||
# only valid for private datasets outside an organization. In this case, a wanr will return
|
||||
# but the process will continue
|
||||
# WARN: This exception should not be risen anymore since public datasets are not updated.
|
||||
message = '%s(%s): %s' % (dataset_id, constants.ALLOWED_USERS, e.error_dict[constants.ALLOWED_USERS][0])
|
||||
log.warn(message)
|
||||
warns.append(message)
|
||||
|
||||
# Return warnings that inform about non-existing datasets
|
||||
if len(warns) > 0:
|
||||
return {'warns': warns}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||
# Copyright (c) 2014 - 2017 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||
|
||||
# This file is part of CKAN Private Dataset Extension.
|
||||
|
||||
|
@ -17,13 +17,15 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with CKAN Private Dataset Extension. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import ckan.authz as authz
|
||||
from ckan.common import _, request
|
||||
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
|
||||
from ckanext.privatedatasets import db
|
||||
|
||||
|
||||
@tk.auth_allow_anonymous_access
|
||||
|
@ -39,41 +41,37 @@ def package_show(context, data_dict):
|
|||
# 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 package.private:
|
||||
|
||||
# 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
|
||||
acquired = 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
|
||||
db.init_db(context['model'])
|
||||
if package.owner_org:
|
||||
acquired = authz.has_user_permission_for_group_or_org(
|
||||
package.owner_org, user, 'read')
|
||||
|
||||
# Branch not executed if the database return an empty list
|
||||
if db.AllowedUser.get(package_id=package.id, user_name=user):
|
||||
authorized = True
|
||||
if not acquired:
|
||||
# Init the model
|
||||
db.init_db(context['model'])
|
||||
|
||||
if not authorized:
|
||||
# Show a flash message with the URL to acquire 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
|
||||
# Branch not executed if the database return an empty list
|
||||
if db.AllowedUser.get(package_id=package.id, user_name=user):
|
||||
acquired = True
|
||||
|
||||
if hasattr(package, 'extras') and 'acquire_url' in package.extras and request.path.startswith('/dataset/')\
|
||||
and package.extras['acquire_url'] != '':
|
||||
helpers.flash_notice(_('This private dataset can be acquired. To do so, please click ' +
|
||||
'<a target="_blank" href="%s">here</a>') % package.extras['acquire_url'],
|
||||
allow_html=True)
|
||||
if not acquired:
|
||||
|
||||
return {'success': False, 'msg': _('User %s not authorized to read package %s') % (user, package.id)}
|
||||
else:
|
||||
return {'success': True}
|
||||
# Show a flash message with the URL to acquire 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 'acquire_url' in package.extras and request.path.startswith(
|
||||
'/dataset/') \
|
||||
and package.extras['acquire_url'] != '':
|
||||
helpers.flash_notice(_('This private dataset can be acquired. To do so, please click ' +
|
||||
'<a target="_blank" href="%s">here</a>') % package.extras['acquire_url'],
|
||||
allow_html=True)
|
||||
|
||||
return {'success': True}
|
||||
else:
|
||||
return {'success': False, 'msg': _('User %s not authorized to read package %s') % (user, package.id)}
|
||||
|
||||
|
@ -89,7 +87,7 @@ def package_update(context, data_dict):
|
|||
|
||||
# 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(
|
||||
authorized = authz.has_user_permission_for_group_or_org(
|
||||
package.owner_org, user, 'update_dataset')
|
||||
else:
|
||||
authorized = False
|
||||
|
@ -102,38 +100,62 @@ def package_update(context, data_dict):
|
|||
|
||||
@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 of 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
|
||||
# It's fixed now, so this function can be deleted when the new version is released.
|
||||
_model = context['model']
|
||||
user = context.get('user')
|
||||
resource = logic_auth.get_resource_object(context, data_dict)
|
||||
|
||||
user = context.get('user')
|
||||
user_obj = context.get('auth_user_obj')
|
||||
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:
|
||||
package_dict = {'id': resource.package_id}
|
||||
package = logic_auth.get_package_object(context, package_dict)
|
||||
if not package:
|
||||
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:
|
||||
if package and user_obj and package.creator_user_id == user_obj.id:
|
||||
return {'success': True}
|
||||
|
||||
# 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 = authz.has_user_permission_for_group_or_org(
|
||||
package.owner_org, user, 'read')
|
||||
else:
|
||||
authorized = False
|
||||
|
||||
if not authorized:
|
||||
# Init the model
|
||||
db.init_db(context['model'])
|
||||
|
||||
# Branch not executed if the database return an empty list
|
||||
if db.AllowedUser.get(package_id=package.id, user_name=user):
|
||||
authorized = True
|
||||
|
||||
if not authorized:
|
||||
return {'success': False, 'msg': _('User %s not authorized to read resource %s') % (user, resource.id)}
|
||||
|
||||
else:
|
||||
return {'success': True}
|
||||
|
||||
else:
|
||||
return {'success': False, 'msg': _('User %s not authorized to read resource %s') % (user, resource.id)}
|
||||
|
||||
@tk.auth_allow_anonymous_access
|
||||
def package_acquired(context, data_dict):
|
||||
# TODO: Improve security
|
||||
return {'success': True}
|
||||
|
||||
|
||||
def acquisitions_list(context, data_dict):
|
||||
# Users can get only their acquisitions list
|
||||
return {'success': context['user'] == data_dict['user']}
|
||||
|
||||
|
||||
@tk.auth_allow_anonymous_access
|
||||
def revoke_access(context, data_dict):
|
||||
# TODO: Check functionality and improve security(if needed)
|
||||
return {'success': True}
|
||||
|
|
|
@ -24,3 +24,4 @@ SEARCHABLE = 'searchable'
|
|||
ACQUIRE_URL = 'acquire_url'
|
||||
CONTEXT_CALLBACK = 'updating_via_cb'
|
||||
PACKAGE_ACQUIRED = 'package_acquired'
|
||||
PACKAGE_DELETED = 'revoke_access'
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ckanext.privatedatasets.constants as constants
|
||||
import ckan.lib.base as base
|
||||
import ckan.model as model
|
||||
import ckan.plugins as plugins
|
||||
import logging
|
||||
|
||||
from ckan.common import _
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AcquiredDatasetsControllerUI(base.BaseController):
|
||||
|
||||
def user_acquired_datasets(self):
|
||||
|
||||
c = plugins.toolkit.c
|
||||
context = {
|
||||
'model': model,
|
||||
'session': model.Session,
|
||||
'user': plugins.toolkit.c.user,
|
||||
}
|
||||
|
||||
# Get user information
|
||||
try:
|
||||
c.user_dict = plugins.toolkit.get_action('user_show')(context.copy(), {'user_obj': c.userobj})
|
||||
c.user_dict['acquired_datasets'] = plugins.toolkit.get_action(constants.ACQUISITIONS_LIST)(context.copy(), None)
|
||||
except plugins.toolkit.ObjectNotFound:
|
||||
plugins.toolkit.abort(404, _('User not found'))
|
||||
except plugins.toolkit.NotAuthorized:
|
||||
plugins.toolkit.abort(401, _('Not authorized to see this page'))
|
||||
|
||||
return plugins.toolkit.render('user/dashboard_acquired.html')
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||
# Copyright (c) 2019 Future Internet Consulting and Development Solutions S.L.
|
||||
|
||||
# This file is part of CKAN Private Dataset Extension.
|
||||
|
||||
|
@ -17,13 +18,16 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with CKAN Private Dataset Extension. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import constants
|
||||
import db
|
||||
from __future__ import absolute_import
|
||||
|
||||
from itertools import count
|
||||
import re
|
||||
|
||||
from ckan.plugins import toolkit
|
||||
from ckan.common import _
|
||||
from itertools import count
|
||||
import six
|
||||
|
||||
from ckanext.privatedatasets import constants, db
|
||||
|
||||
|
||||
def private_datasets_metadata_checker(key, data, errors, context):
|
||||
|
@ -33,7 +37,7 @@ def private_datasets_metadata_checker(key, data, errors, context):
|
|||
|
||||
# Avoid missing value
|
||||
# "if not private_val:" is not valid because private_val can be False
|
||||
if not isinstance(private_val, basestring) and not isinstance(private_val, bool):
|
||||
if not isinstance(private_val, six.string_types) and not isinstance(private_val, bool):
|
||||
private_val = None
|
||||
|
||||
# If the private field is not included in the data dict, we must check the current value
|
||||
|
@ -57,7 +61,7 @@ def allowed_users_convert(key, data, errors, context):
|
|||
# Get the allowed user list
|
||||
if (constants.ALLOWED_USERS,) in data and isinstance(data[(constants.ALLOWED_USERS,)], list):
|
||||
allowed_users = data[(constants.ALLOWED_USERS,)]
|
||||
elif (constants.ALLOWED_USERS_STR,) in data and isinstance(data[(constants.ALLOWED_USERS_STR,)], basestring):
|
||||
elif (constants.ALLOWED_USERS_STR,) in data and isinstance(data[(constants.ALLOWED_USERS_STR,)], six.string_types):
|
||||
allowed_users_str = data[(constants.ALLOWED_USERS_STR,)].strip()
|
||||
allowed_users = [allowed_user for allowed_user in allowed_users_str.split(',') if allowed_user.strip() != '']
|
||||
else:
|
||||
|
@ -71,7 +75,6 @@ def allowed_users_convert(key, data, errors, context):
|
|||
else:
|
||||
for num, allowed_user in zip(count(current_index + 1), allowed_users):
|
||||
allowed_user = allowed_user.strip()
|
||||
toolkit.get_validator('name_validator')(allowed_user, context) # User name should be validated
|
||||
data[(key[0], num)] = allowed_user
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with CKAN Private Dataset Extension. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
AllowedUser = None
|
||||
|
@ -38,7 +40,9 @@ def init_db(model):
|
|||
AllowedUser = _AllowedUser
|
||||
|
||||
# FIXME: Maybe a default value should not be included...
|
||||
package_allowed_users_table = sa.Table('package_allowed_users', model.meta.metadata,
|
||||
package_allowed_users_table = sa.Table(
|
||||
'package_allowed_users',
|
||||
model.meta.metadata,
|
||||
sa.Column('package_id', sa.types.UnicodeText, primary_key=True, default=u''),
|
||||
sa.Column('user_name', sa.types.UnicodeText, primary_key=True, default=u''),
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014-2015 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.
|
||||
|
||||
|
@ -17,15 +18,18 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with CKAN Private Dataset Extension. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ckan.model as model
|
||||
import ckan.plugins.toolkit as tk
|
||||
import db
|
||||
|
||||
from pylons import config
|
||||
|
||||
from ckan.common import request
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from ckan.common import request
|
||||
import ckan.model as model
|
||||
import ckan.plugins.toolkit as tk
|
||||
|
||||
from ckanext.privatedatasets import db
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -62,9 +66,9 @@ def can_read(pkg_dict):
|
|||
|
||||
|
||||
def get_config_bool_value(config_name, default_value=False):
|
||||
value = config.get(config_name, default_value)
|
||||
value = value if type(value) == bool else value != 'False'
|
||||
return value
|
||||
env_name = config_name.upper().replace('.', '_')
|
||||
value = os.environ.get(env_name, tk.config.get(config_name, default_value))
|
||||
return value if type(value) == bool else value.strip().lower() in ('true', '1', 'on')
|
||||
|
||||
|
||||
def show_acquire_url_on_create():
|
||||
|
|
|
@ -17,23 +17,23 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with CKAN Private Dataset Extension. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ckan.plugins.toolkit as tk
|
||||
import re
|
||||
|
||||
from urlparse import urlparse
|
||||
|
||||
from ckan.common import request
|
||||
import ckan.plugins.toolkit as tk
|
||||
import six
|
||||
|
||||
|
||||
class FiWareNotificationParser(object):
|
||||
|
||||
def parse_notification(self, request_data):
|
||||
|
||||
my_host = request.host
|
||||
|
||||
fields = ['customer_name', 'resources']
|
||||
|
||||
for field in fields:
|
||||
if not field in request_data:
|
||||
if field not in request_data:
|
||||
raise tk.ValidationError({'message': '%s not found in the request' % field})
|
||||
|
||||
# Parse the body
|
||||
|
@ -41,7 +41,7 @@ class FiWareNotificationParser(object):
|
|||
user_name = request_data['customer_name']
|
||||
datasets = []
|
||||
|
||||
if not isinstance(user_name, basestring):
|
||||
if not isinstance(user_name, six.string_types):
|
||||
raise tk.ValidationError({'message': 'Invalid customer_name format'})
|
||||
|
||||
if not isinstance(resources, list):
|
||||
|
@ -52,12 +52,18 @@ class FiWareNotificationParser(object):
|
|||
parsed_url = urlparse(resource['url'])
|
||||
dataset_name = re.findall('^/dataset/([^/]+).*$', parsed_url.path)
|
||||
|
||||
resource_url = parsed_url.netloc
|
||||
if ':' in my_host and ':' not in resource_url:
|
||||
# Add the default port depending on the protocol
|
||||
default_port = '80' if parsed_url.protocol == 'http' else '443'
|
||||
resource_url = resource_url + default_port
|
||||
|
||||
if len(dataset_name) == 1:
|
||||
if parsed_url.netloc == my_host:
|
||||
if resource_url == my_host:
|
||||
datasets.append(dataset_name[0])
|
||||
else:
|
||||
raise tk.ValidationError({'message': 'Dataset %s is associated with the CKAN instance located at %s'
|
||||
% (dataset_name[0], parsed_url.netloc)})
|
||||
raise tk.ValidationError({'message': 'Dataset %s is associated with the CKAN instance located at %s, expected %s'
|
||||
% (dataset_name[0], resource_url, my_host)})
|
||||
else:
|
||||
raise tk.ValidationError({'message': 'Invalid resource format'})
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||
# 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.
|
||||
|
||||
|
@ -17,30 +18,33 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with CKAN Private Dataset Extension. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ckan.lib.search as search
|
||||
import ckan.model as model
|
||||
import ckan.plugins as p
|
||||
import ckan.plugins.toolkit as tk
|
||||
import auth
|
||||
import actions
|
||||
import constants
|
||||
import converters_validators as conv_val
|
||||
import db
|
||||
import helpers as helpers
|
||||
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):
|
||||
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.IRoutes, inherit=True)
|
||||
p.implements(p.IActions)
|
||||
p.implements(p.IPackageController, inherit=True)
|
||||
p.implements(p.ITemplateHelpers)
|
||||
p.implements(p.IPermissionLabels)
|
||||
p.implements(p.IResourceController)
|
||||
|
||||
######################################################################
|
||||
############################ DATASET FORM ############################
|
||||
|
@ -110,14 +114,10 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
|
|||
def get_auth_functions(self):
|
||||
auth_functions = {'package_show': auth.package_show,
|
||||
'package_update': auth.package_update,
|
||||
# 'resource_show': auth.resource_show,
|
||||
'resource_show': auth.resource_show,
|
||||
constants.PACKAGE_ACQUIRED: auth.package_acquired,
|
||||
constants.ACQUISITIONS_LIST: auth.acquisitions_list}
|
||||
|
||||
# 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
|
||||
constants.ACQUISITIONS_LIST: auth.acquisitions_list,
|
||||
constants.PACKAGE_DELETED: auth.revoke_access}
|
||||
|
||||
return auth_functions
|
||||
|
||||
|
@ -128,32 +128,42 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
|
|||
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')
|
||||
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('fanstatic', 'privatedatasets')
|
||||
tk.add_resource(b'fanstatic', b'privatedatasets')
|
||||
|
||||
######################################################################
|
||||
############################## IROUTES ###############################
|
||||
############################# IBLUEPRINT #############################
|
||||
######################################################################
|
||||
|
||||
# Deprecated but Required for CKAN 2.7
|
||||
def before_map(self, m):
|
||||
# DataSet acquired notification
|
||||
m.connect('user_acquired_datasets', '/dashboard/acquired', ckan_icon='shopping-cart',
|
||||
controller='ckanext.privatedatasets.controllers.ui_controller:AcquiredDatasetsControllerUI',
|
||||
action='user_acquired_datasets', conditions=dict(method=['GET']))
|
||||
|
||||
if p.toolkit.check_ckan_version(max_version='2.7.99'):
|
||||
m.connect('user_acquired_datasets', '/dashboard/acquired', ckan_icon='shopping-cart',
|
||||
controller='ckanext.privatedatasets.views:AcquiredDatasetsControllerUI',
|
||||
action='acquired_datasets', conditions=dict(method=['GET']))
|
||||
return m
|
||||
|
||||
def get_blueprint(self):
|
||||
blueprint = Blueprint('privatedatasets', self.__module__)
|
||||
if p.toolkit.check_ckan_version(min_version='2.8'):
|
||||
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
|
||||
}
|
||||
action_functions = {constants.PACKAGE_ACQUIRED: actions.package_acquired,
|
||||
constants.ACQUISITIONS_LIST: actions.acquisitions_list,
|
||||
constants.PACKAGE_DELETED: actions.revoke_access}
|
||||
|
||||
return action_functions
|
||||
|
||||
######################################################################
|
||||
######################### IPACKAGECONTROLLER #########################
|
||||
|
@ -218,8 +228,10 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
|
|||
'validate': False,
|
||||
'use_cache': False},
|
||||
{'id': package_id})
|
||||
|
||||
# Prevent acquired datasets jumping to the first position
|
||||
new_pkg_dict['metadata_modified'] = new_pkg_dict.get('revision_timestamp', '')
|
||||
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
|
||||
|
@ -229,6 +241,16 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
|
|||
|
||||
def after_show(self, context, pkg_dict):
|
||||
|
||||
void = False;
|
||||
|
||||
for resource in pkg_dict['resources']:
|
||||
if resource == {}:
|
||||
void = True
|
||||
|
||||
if void:
|
||||
del pkg_dict['resources']
|
||||
del pkg_dict['num_resources']
|
||||
|
||||
user_obj = context.get('auth_user_obj')
|
||||
updating_via_api = context.get(constants.CONTEXT_CALLBACK, False)
|
||||
|
||||
|
@ -260,7 +282,7 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
|
|||
|
||||
def after_search(self, search_results, search_params):
|
||||
for result in search_results['results']:
|
||||
# Extra fields should not be returned
|
||||
# Extra fields should not be returned
|
||||
# The original list cannot be modified
|
||||
attrs = list(HIDDEN_FIELDS)
|
||||
|
||||
|
@ -279,12 +301,74 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm):
|
|||
# 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 before_view(self, pkg_dict):
|
||||
|
||||
for resource in pkg_dict['resources']:
|
||||
|
||||
context = {
|
||||
'model': model,
|
||||
'session': model.Session,
|
||||
'user': tk.c.user,
|
||||
'user_obj': tk.c.userobj
|
||||
}
|
||||
|
||||
try:
|
||||
tk.check_access('resource_show', context, resource)
|
||||
except tk.NotAuthorized:
|
||||
pkg_dict['resources'].remove(resource)
|
||||
pkg_dict = self.before_view(pkg_dict)
|
||||
return pkg_dict
|
||||
|
||||
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
|
||||
|
||||
######################################################################
|
||||
######################### IRESOURCECONTROLLER ########################
|
||||
######################################################################
|
||||
|
||||
def before_create(self, context, resource):
|
||||
pass
|
||||
|
||||
def before_update(self, context, current, resource):
|
||||
pass
|
||||
|
||||
def before_delete(self, context, resource, resources):
|
||||
pass
|
||||
|
||||
def before_show(self, resource_dict):
|
||||
|
||||
context = {
|
||||
'model': model,
|
||||
'session': model.Session,
|
||||
'user': tk.c.user,
|
||||
'user_obj': tk.c.userobj
|
||||
}
|
||||
|
||||
try:
|
||||
tk.check_access('resource_show', context, resource_dict)
|
||||
except tk.NotAuthorized:
|
||||
resource_dict.clear()
|
||||
return resource_dict
|
||||
|
||||
######################################################################
|
||||
######################### ITEMPLATESHELPER ###########################
|
||||
######################################################################
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
{% endfor %}
|
||||
</select>
|
||||
<span class="info-block info-inline">
|
||||
<i class="icon-info-sign"></i>
|
||||
<i class="icon-info-sign fa fa-info-circle"></i>
|
||||
{% trans %}
|
||||
Private datasets can only be accessed by certain users, while public datasets can be accessed by anyone.
|
||||
{% endtrans %}
|
||||
|
@ -68,7 +68,7 @@
|
|||
{% endfor %}
|
||||
</select>
|
||||
<span class="info-block info-inline">
|
||||
<i class="icon-info-sign"></i>
|
||||
<i class="icon-info-sign fa fa-info-circle"></i>
|
||||
{% trans %}
|
||||
Searchable datasets can be searched by anyone, while not-searchable datasets can only be accessed by entering directly its URL.
|
||||
{% endtrans %}
|
||||
|
|
|
@ -10,6 +10,6 @@ Example:
|
|||
|
||||
#}
|
||||
<a href={{ url_dest }} class="btn btn-mini" target="_blank">
|
||||
<i class="icon-shopping-cart"></i>
|
||||
<i class="icon-shopping-cart fa fa-shopping-cart"></i>
|
||||
{{ _('Acquire') }}
|
||||
</a>
|
||||
|
|
|
@ -26,28 +26,28 @@ Example:
|
|||
{% block package_item_content %}
|
||||
<div class="dataset-content">
|
||||
<h3 class="dataset-heading">
|
||||
{% if package.private and not h.can_read(package) %}
|
||||
{% if package.private and not owner and not acquired %}
|
||||
<span class="dataset-private label label-inverse">
|
||||
<i class="icon-lock"></i>
|
||||
<i class="icon-lock fa fa-lock"></i>
|
||||
{{ _('Private') }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if acquired and not owner %}
|
||||
<span class="dataset-private label label-acquired">
|
||||
<i class="icon-shopping-cart"></i>
|
||||
<i class="icon-shopping-cart fa fa-shopping-cart"></i>
|
||||
{{ _('Acquired') }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if owner %}
|
||||
<span class="dataset-private label label-owner">
|
||||
<i class="icon-user"></i>
|
||||
<i class="icon-user fa fa-user"></i>
|
||||
{{ _('Owner') }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<!-- Customizations Acquire Button -->
|
||||
{% if package.private and not h.can_read(package) %}
|
||||
{{ _(h.truncate(title, truncate_title)) }}
|
||||
{% if package.private and not owner and not acquired %}
|
||||
{{ h.link_to(h.truncate(title, truncate_title), h.url_for(controller='package', action='read', id=package.name)) }}
|
||||
<div class="divider"/>
|
||||
{{ h.acquire_button(package) }}
|
||||
{% else %}
|
||||
|
|
|
@ -46,4 +46,4 @@
|
|||
{% endblock %}
|
||||
</div>
|
||||
</article>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
|
||||
{% block primary_content_inner %}
|
||||
<h2 class="hide-heading">{{ _('Acquired Datasets') }}</h2>
|
||||
{% if c.user_dict.acquired_datasets %}
|
||||
{% snippet 'snippets/package_list.html', packages=c.user_dict.acquired_datasets %}
|
||||
{% if acquired_datasets %}
|
||||
{% snippet 'snippets/package_list.html', packages=acquired_datasets %}
|
||||
{% else %}
|
||||
<p class="empty">
|
||||
{{ _('You haven\'t acquired any datasets.') }}
|
||||
{% link_for _('Acquire one now?'), controller='package', action='search' %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
{% ckan_extends %}
|
||||
|
||||
{% block package_basic_fields_org %}
|
||||
|
||||
{% resource 'privatedatasets/allowed_users.js' %}
|
||||
|
||||
{# if we have a default group then this wants remembering #}
|
||||
{% if data.group_id %}
|
||||
<input type="hidden" name="groups__0__id" value="{{ data.group_id }}" />
|
||||
{% endif %}
|
||||
|
||||
{% set dataset_is_draft = data.get('state', 'draft').startswith('draft') or data.get('state', 'none') == 'none' %}
|
||||
{% set dataset_has_organization = data.owner_org or data.group_id %}
|
||||
{% set organizations_available = h.organizations_available('create_dataset') %}
|
||||
{% set user_is_sysadmin = h.check_access('sysadmin') %}
|
||||
{% set show_organizations_selector = organizations_available and (user_is_sysadmin or dataset_is_draft) %}
|
||||
{% set editing = 'id' in data %}
|
||||
|
||||
{% if show_organizations_selector and show_visibility_selector %}
|
||||
<div>
|
||||
{% endif %}
|
||||
|
||||
{% if show_organizations_selector %}
|
||||
{% set existing_org = data.owner_org or data.group_id %}
|
||||
<div class="control-group">
|
||||
<label for="field-organizations" class="control-label">{{ _('Organization') }}</label>
|
||||
<div class="controls">
|
||||
<select id="field-organizations" name="owner_org" data-module="autocomplete">
|
||||
{% if h.check_config_permission('create_unowned_dataset') %}
|
||||
<option value="" {% if not selected_org and data.id %} selected="selected" {% endif %}>{{ _('No organization') }}</option>
|
||||
{% endif %}
|
||||
{% for organization in organizations_available %}
|
||||
{# get out first org from users list only if there is not an existing org #}
|
||||
{% set selected_org = (existing_org and existing_org == organization.id) or (not existing_org and not data.id and organization.id == organizations_available[0].id) %}
|
||||
<option value="{{ organization.id }}" {% if selected_org %} selected="selected" {% endif %}>{{ organization.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block package_metadata_fields_visibility %}
|
||||
<div class="control-group">
|
||||
<label for="field-private" class="control-label">{{ _('Visibility') }}</label>
|
||||
<div class="controls">
|
||||
<select id="field-private" name="private" data-module="allowed-users">
|
||||
{% for option in [('True', _('Private')), ('False', _('Public'))] %}
|
||||
<option value="{{ option[0] }}" {% if option[0] == data.private|trim %}selected="selected"{% endif %}>{{ option[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<span class="info-block info-inline">
|
||||
<i class="icon-info-sign fa fa-info-circle"></i>
|
||||
{% trans %}
|
||||
Private datasets can only be accessed by certain users, while public datasets can be accessed by anyone.
|
||||
{% endtrans %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block package_metadata_fields_protected %}
|
||||
<div class="control-group">
|
||||
<label for="field-searchable" class="control-label">{{ _('Searchable') }}</label>
|
||||
<div class="controls">
|
||||
<select id="field-searchable" name="searchable">
|
||||
{% for option in [('True', _('True')), ('False', _('False'))] %}
|
||||
<option value="{{ option[0] }}" {% if option[0] == data.searchable|trim %}selected="selected"{% endif %}>{{ option[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<span class="info-block info-inline">
|
||||
<i class="icon-info-sign fa fa-info-circle"></i>
|
||||
{% trans %}
|
||||
Searchable datasets can be searched by anyone, while not-searchable datasets can only be accessed by entering directly its URL.
|
||||
{% endtrans %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% if show_organizations_selector and show_visibility_selector %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set users_attrs = {'data-module': 'autocomplete', 'data-module-tags': '', 'data-module-source': '/api/2/util/user/autocomplete?q=?'} %}
|
||||
{{ 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) }}
|
||||
|
||||
|
||||
{% if editing and h.show_acquire_url_on_edit() or not editing and h.show_acquire_url_on_create() %}
|
||||
{{ form.input('acquire_url', label=_('Acquire URL'), id='field-acquire_url', placeholder=_('http://example.com/acquire/'), value=data.acquire_url, error=errors.custom_text, classes=['control-medium']) }}
|
||||
{% else %}
|
||||
<input type="hidden" name="acquire_url" id="acquire_url" value="{{ data.acquire_url }}" />
|
||||
{% endif %}
|
||||
|
||||
{% if data.id and h.check_access('package_delete', {'id': data.id}) and data.state != 'active' %}
|
||||
<div class="control-group">
|
||||
<label for="field-state" class="control-label">{{ _('State') }}</label>
|
||||
<div class="controls">
|
||||
<select id="field-state" name="state">
|
||||
<option value="active" {% if data.get('state', 'none') == 'active' %} selected="selected" {% endif %}>{{ _('Active') }}</option>
|
||||
<option value="deleted" {% if data.get('state', 'none') == 'deleted' %} selected="selected" {% endif %}>{{ _('Deleted') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,15 @@
|
|||
{#
|
||||
|
||||
Displays a Get Access button to request access to a private dataset.
|
||||
|
||||
ulr_dest - target url
|
||||
|
||||
Example:
|
||||
|
||||
{% snippet 'snippets/acquire_button.html', url_dest=url %}
|
||||
|
||||
#}
|
||||
<a href={{ url_dest }} class="btn btn-mini" target="_blank">
|
||||
<i class="icon-shopping-cart fa fa-shopping-cart"></i>
|
||||
{{ _('Acquire') }}
|
||||
</a>
|
|
@ -0,0 +1,82 @@
|
|||
{#
|
||||
Displays a single of dataset.
|
||||
|
||||
package - A package to display.
|
||||
item_class - The class name to use on the list item.
|
||||
hide_resources - If true hides the resources (default: false).
|
||||
banner - If true displays a popular banner (default: false).
|
||||
truncate - The length to trucate the description to (default: 180)
|
||||
truncate_title - The length to truncate the title to (default: 80).
|
||||
|
||||
Example:
|
||||
|
||||
{% snippet 'snippets/package_item.html', package=c.datasets[0] %}
|
||||
|
||||
#}
|
||||
{% set truncate = truncate or 180 %}
|
||||
{% set truncate_title = truncate_title or 80 %}
|
||||
{% set title = package.title or package.name %}
|
||||
{% set notes = h.markdown_extract(package.notes, extract_length=truncate) %}
|
||||
{% set acquired = h.is_dataset_acquired(package) %}
|
||||
{% set owner = h.is_owner(package) %}
|
||||
|
||||
{% resource 'privatedatasets/custom.css' %}
|
||||
|
||||
<li class="{{ item_class or "dataset-item" }}">
|
||||
{% block package_item_content %}
|
||||
<div class="dataset-content">
|
||||
<h3 class="dataset-heading">
|
||||
{% if package.private and not owner and not acquired%}
|
||||
<span class="dataset-private label label-inverse">
|
||||
<i class="icon-lock fa fa-lock"></i>
|
||||
{{ _('Private') }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if acquired and not owner %}
|
||||
<span class="dataset-private label label-acquired">
|
||||
<i class="icon-shopping-cart fa fa-shopping-cart"></i>
|
||||
{{ _('Acquired') }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if owner %}
|
||||
<span class="dataset-private label label-owner">
|
||||
<i class="icon-user fa fa-user"></i>
|
||||
{{ _('Owner') }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<!-- Customizations Acquire Button -->
|
||||
{% if package.private and not owner and not acquired %}
|
||||
{{ h.link_to(h.truncate(title, truncate_title), h.url_for(controller='package', action='read', id=package.name)) }}
|
||||
<div class="divider"/>
|
||||
{{ h.acquire_button(package) }}
|
||||
{% else %}
|
||||
{{ h.link_to(h.truncate(title, truncate_title), h.url_for(controller='package', action='read', id=package.name)) }}
|
||||
{% endif %}
|
||||
<!-- End of customizations Acquire Button -->
|
||||
|
||||
{% if package.get('state', '').startswith('draft') %}
|
||||
<span class="label label-info">{{ _('Draft') }}</span>
|
||||
{% elif package.get('state', '').startswith('deleted') %}
|
||||
<span class="label label-important">{{ _('Deleted') }}</span>
|
||||
{% endif %}
|
||||
{{ h.popular('recent views', package.tracking_summary.recent, min=10) if package.tracking_summary }}
|
||||
</h3>
|
||||
{% if banner %}
|
||||
<span class="banner">{{ _('Popular') }}</span>
|
||||
{% endif %}
|
||||
{% if notes %}
|
||||
<div>{{ notes|urlize }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if package.resources and not hide_resources %}
|
||||
<ul class="dataset-resources unstyled">
|
||||
{% for resource in h.dict_list_reduce(package.resources, 'format') %}
|
||||
<li>
|
||||
<a href="{{ h.url_for(controller='package', action='read', id=package.name) }}" class="label" data-format="{{ resource.lower() }}">{{ resource }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</li>
|
|
@ -0,0 +1,49 @@
|
|||
{% extends "user/edit_base.html" %}
|
||||
|
||||
{% set user = g.userobj %}
|
||||
|
||||
{% block breadcrumb_content %}
|
||||
<li class="active"><a href="{{ h.url_for('dashboard.index') }}">{{ _('Dashboard') }}</a></li>
|
||||
{% endblock %}
|
||||
|
||||
{% block secondary %}{% endblock %}
|
||||
|
||||
{% block primary %}
|
||||
<article class="module">
|
||||
{% block page_header %}
|
||||
<header class="module-content page-header hug">
|
||||
<div class="content_action">
|
||||
{% link_for _('Edit settings'), named_route='user.edit', id=user.name, class_='btn btn-default', icon='cog' %}
|
||||
</div>
|
||||
<ul class="nav nav-tabs">
|
||||
{{ h.build_nav_icon('dashboard.index', _('News feed')) }}
|
||||
{{ h.build_nav_icon('dashboard.datasets', _('My Datasets')) }}
|
||||
{{ h.build_nav_icon('privatedatasets.acquired_datasets', _('Acquired Datasets')) }}
|
||||
{{ h.build_nav_icon('dashboard.organizations', _('My Organizations')) }}
|
||||
{{ h.build_nav_icon('dashboard.groups', _('My Groups')) }}
|
||||
</ul>
|
||||
</header>
|
||||
{% endblock %}
|
||||
<div class="module-content">
|
||||
{% if self.page_primary_action() | trim %}
|
||||
<div class="page_primary_action">
|
||||
{% block page_primary_action %}{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block primary_content_inner %}
|
||||
<div data-module="dashboard">
|
||||
{% snippet 'user/snippets/followee_dropdown.html', context=dashboard_activity_stream_context, followees=followee_list %}
|
||||
<h2 class="page-heading">
|
||||
{% block page_heading %}
|
||||
{{ _('News feed') }}
|
||||
{% endblock %}
|
||||
<small>{{ _("Activity from items that I'm following") }}</small>
|
||||
</h2>
|
||||
{% block activity_stream %}
|
||||
{{ dashboard_activity_stream|safe }}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</article>
|
||||
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "user/dashboard.html" %}
|
||||
|
||||
{% block dashboard_activity_stream_context %}{% endblock %}
|
||||
|
||||
{% block page_primary_action %}
|
||||
{% link_for _('Acquire Dataset'), controller='package', action='search', class_="btn btn-primary", icon="shopping-cart" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block primary_content_inner %}
|
||||
<h2 class="hide-heading">{{ _('Acquired Datasets') }}</h2>
|
||||
{% if acquired_datasets %}
|
||||
{% snippet 'snippets/package_list.html', packages=acquired_datasets %}
|
||||
{% else %}
|
||||
<p class="empty">
|
||||
{{ _('You haven\'t acquired any datasets.') }}
|
||||
{% link_for _('Acquire one now?'), controller='package', action='search' %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -21,7 +21,7 @@ import ckanext.privatedatasets.actions as actions
|
|||
import unittest
|
||||
|
||||
from mock import MagicMock
|
||||
from nose_parameterized import parameterized
|
||||
from parameterized import parameterized
|
||||
|
||||
PARSER_CONFIG_PROP = 'ckan.privatedatasets.parser'
|
||||
IMPORT_ERROR_MSG = 'Unable to load the module'
|
||||
|
@ -34,9 +34,6 @@ class ActionsTest(unittest.TestCase):
|
|||
def setUp(self):
|
||||
|
||||
# Load the mocks
|
||||
self._config = actions.config
|
||||
actions.config = MagicMock()
|
||||
|
||||
self._importlib = actions.importlib
|
||||
actions.importlib = MagicMock()
|
||||
|
||||
|
@ -48,7 +45,6 @@ class ActionsTest(unittest.TestCase):
|
|||
|
||||
def tearDown(self):
|
||||
# Unmock
|
||||
actions.config = self._config
|
||||
actions.importlib = self._importlib
|
||||
actions.plugins = self._plugins
|
||||
actions.db = self._db
|
||||
|
@ -64,10 +60,9 @@ class ActionsTest(unittest.TestCase):
|
|||
|
||||
])
|
||||
def test_class_cannot_be_loaded(self, class_path, class_name, path_exist, class_exist, expected_error):
|
||||
|
||||
class_package = class_path
|
||||
class_package += ':' + class_name if class_name else ''
|
||||
actions.config = {PARSER_CONFIG_PROP: class_package}
|
||||
actions.plugins.toolkit.config = {PARSER_CONFIG_PROP: class_package}
|
||||
|
||||
# Recover exception
|
||||
actions.plugins.toolkit.ValidationError = self._plugins.toolkit.ValidationError
|
||||
|
@ -79,8 +74,7 @@ class ActionsTest(unittest.TestCase):
|
|||
|
||||
actions.importlib.import_module = MagicMock(side_effect=ImportError(IMPORT_ERROR_MSG) if not path_exist else None,
|
||||
return_value=package if path_exist else None)
|
||||
|
||||
# Call the function
|
||||
|
||||
if expected_error:
|
||||
with self.assertRaises(actions.plugins.toolkit.ValidationError) as cm:
|
||||
actions.package_acquired({}, {})
|
||||
|
@ -95,7 +89,7 @@ class ActionsTest(unittest.TestCase):
|
|||
def configure_mocks(self, parse_result, datasets_not_found=[], not_updatable_datasets=[],
|
||||
allowed_users=None, creator_user={'id': '1234', 'name': 'ckan'}):
|
||||
|
||||
actions.config = {PARSER_CONFIG_PROP: 'valid.path:%s' % CLASS_NAME}
|
||||
actions.plugins.toolkit.config = {PARSER_CONFIG_PROP: 'valid.path:%s' % CLASS_NAME}
|
||||
|
||||
# Configure mocks
|
||||
parser_instance = MagicMock()
|
||||
|
@ -163,18 +157,14 @@ class ActionsTest(unittest.TestCase):
|
|||
])
|
||||
def test_add_users(self, users_info, datasets_not_found, not_updatable_datasets, allowed_users=[]):
|
||||
|
||||
parse_result = {'users_datasets': []}
|
||||
parse_result = {'users_datasets': [{'user': user, 'datasets': users_info[user]} for user in users_info]}
|
||||
creator_user = {'name': 'ckan', 'id': '1234'}
|
||||
|
||||
# Transform user_info
|
||||
for user in users_info:
|
||||
parse_result['users_datasets'].append({'user': user, 'datasets': users_info[user]})
|
||||
|
||||
parse_notification, package_show, package_update, user_show = self.configure_mocks(parse_result,
|
||||
datasets_not_found, not_updatable_datasets, allowed_users, creator_user)
|
||||
|
||||
# Call the function
|
||||
context = {'user': 'user1', 'model': 'model', 'auth_obj': {'id': 1}}
|
||||
context = {'user': 'user1', 'model': 'model', 'auth_obj': {'id': 1}, 'method': 'grant'}
|
||||
result = actions.package_acquired(context, users_info)
|
||||
|
||||
# Calculate the list of warns
|
||||
|
@ -295,3 +285,80 @@ class ActionsTest(unittest.TestCase):
|
|||
expected_acquired_datasets.append(pkg)
|
||||
|
||||
self.assertEquals(expected_acquired_datasets, result)
|
||||
|
||||
@parameterized.expand([
|
||||
# Simple Test: one user and one dataset
|
||||
({'user1': ['ds1']}, [], [], None), #Test with and non-existing list of allowed users
|
||||
({'user2': ['ds1']}, [], [], []), #Test remove a non-existing user
|
||||
({'user3': ['ds1']}, [], [], ['user3']), #Test remove an existing user
|
||||
({'user4': ['ds1']}, [], [], ['another_user']), #Test remove non-existing from an already populated list
|
||||
({'user5': ['ds1']}, [], [], ['another_user', 'user5']),
|
||||
({'user6': ['ds1']}, ['ds1'], [], None), #Test remove from an unknown place
|
||||
({'user61': ['ds1']}, ['ds1'], [], []),
|
||||
({'user62': ['ds1']}, ['ds1'], [], ['user6']),
|
||||
({'user7': ['ds1']}, [], ['ds1'], []), #Tests deleting from a public dataset
|
||||
({'user8': ['ds1']}, [], ['ds1'], ['another_user']),
|
||||
({'user9': ['ds1']}, [], ['ds1'], ['another_user', 'another_one']),
|
||||
({'user91': ['ds1']}, ['ds1'], ['ds1'], ['another_user', 'another_one']), # Checking the behaviour when the unknown dataset is public
|
||||
({'user92': ['ds1']}, ['ds1'], ['ds1'], ['another_user', 'another_one','user92']),
|
||||
|
||||
# Complex test: some users and some datasets
|
||||
({'user1': ['ds1', 'ds2', 'ds3', 'ds4'], 'user2': ['ds5', 'ds6', 'ds7']}, ['ds3', 'ds6'], ['ds4', 'ds7'], []),
|
||||
({'user3': ['ds1', 'ds2', 'ds3', 'ds4'], 'user4': ['ds5', 'ds6', 'ds7']}, ['ds3', 'ds6'], ['ds4', 'ds7'], ['another_user']),
|
||||
({'user5': ['ds1', 'ds2', 'ds3', 'ds4'], 'user6': ['ds5', 'ds6', 'ds7']}, ['ds3', 'ds6'], ['ds4', 'ds7'], ['another_user', 'another_one']),
|
||||
({'user7': ['ds1', 'ds2', 'ds3', 'ds4'], 'user8': ['ds5', 'ds6', 'ds7']}, ['ds3', 'ds6'], ['ds4', 'ds7'], ['another_user', 'another_one', 'user7'])
|
||||
])
|
||||
def test_delete_users(self, users_info, datasets_not_found, not_updatable_datasets, allowed_users=[]):
|
||||
parse_result = {'users_datasets': []}
|
||||
creator_user = {'name': 'ckan', 'id': '1234'}
|
||||
|
||||
# Transform user_info
|
||||
for user in users_info:
|
||||
parse_result['users_datasets'].append({'user': user, 'datasets': users_info[user]})
|
||||
|
||||
parse_delete, package_show, package_update, user_show = self.configure_mocks(parse_result,
|
||||
datasets_not_found, not_updatable_datasets, allowed_users, creator_user)
|
||||
|
||||
# Call the function
|
||||
context = {'user': 'user1', 'model': 'model', 'auth_obj': {'id': 1}, 'method': 'revoke'}
|
||||
result = actions.revoke_access(context, users_info)
|
||||
|
||||
# Calculate the list of warns
|
||||
warns = []
|
||||
for user_datasets in parse_result['users_datasets']:
|
||||
for dataset_id in user_datasets['datasets']:
|
||||
if dataset_id in datasets_not_found:
|
||||
warns.append('Dataset %s was not found in this instance' % dataset_id)
|
||||
elif dataset_id in not_updatable_datasets:
|
||||
# warns.append('%s(%s): %s' % (dataset_id, 'allowed_users', ADD_USERS_ERROR))
|
||||
warns.append('Unable to upload the dataset %s: It\'s a public dataset' % dataset_id)
|
||||
|
||||
expected_result = {'warns': warns} if len(warns) > 0 else None
|
||||
|
||||
# Check that the returned result is as expected
|
||||
self.assertEquals(expected_result, result)
|
||||
|
||||
# Check that the initial functions (check_access and parse_notification) has been called properly
|
||||
parse_delete.assert_called_once_with(users_info)
|
||||
actions.plugins.toolkit.check_access.assert_called_once_with('revoke_access', context, users_info)
|
||||
|
||||
for user_datasets in parse_result['users_datasets']:
|
||||
for dataset_id in user_datasets['datasets']:
|
||||
# The show function is always called
|
||||
context_show = context.copy()
|
||||
context_show['ignore_auth'] = True
|
||||
context_show['updating_via_cb'] = True
|
||||
package_show.assert_any_call(context_show, {'id': dataset_id})
|
||||
|
||||
# The update function is called only when the show function does not throw an exception and
|
||||
# when the user is not in the list of allowed users.
|
||||
if dataset_id not in datasets_not_found and allowed_users is not None and user_datasets['user'] in allowed_users and dataset_id not in not_updatable_datasets:
|
||||
# Calculate the list of allowed_users
|
||||
expected_allowed_users = list(allowed_users)
|
||||
expected_allowed_users.remove(user_datasets['user'])
|
||||
|
||||
context_update = context.copy()
|
||||
context_update['ignore_auth'] = True
|
||||
context_update['user'] = creator_user['name']
|
||||
|
||||
package_update.assert_any_call(context_update, {'id': dataset_id, 'allowed_users': expected_allowed_users, 'private': True, 'creator_user_id': creator_user['id']})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||
# Copyright (c) 2014 - 2017 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||
|
||||
# This file is part of CKAN Private Dataset Extension.
|
||||
|
||||
|
@ -21,7 +21,7 @@ import unittest
|
|||
import ckanext.privatedatasets.auth as auth
|
||||
|
||||
from mock import MagicMock
|
||||
from nose_parameterized import parameterized
|
||||
from parameterized import parameterized
|
||||
|
||||
|
||||
class AuthTest(unittest.TestCase):
|
||||
|
@ -37,8 +37,8 @@ class AuthTest(unittest.TestCase):
|
|||
self._helpers = auth.helpers
|
||||
auth.helpers = MagicMock()
|
||||
|
||||
self._new_authz = auth.new_authz
|
||||
auth.new_authz = MagicMock()
|
||||
self._authz = auth.authz
|
||||
auth.authz = MagicMock()
|
||||
|
||||
self._tk = auth.tk
|
||||
auth.tk = MagicMock()
|
||||
|
@ -50,10 +50,9 @@ class AuthTest(unittest.TestCase):
|
|||
auth.logic_auth = self._logic_auth
|
||||
auth.request = self._request
|
||||
auth.helpers = self._helpers
|
||||
auth.new_authz = self._new_authz
|
||||
auth.authz = self._authz
|
||||
auth.tk = self._tk
|
||||
auth.db = self._db
|
||||
|
||||
if hasattr(self, '_package_show'):
|
||||
auth.package_show = self._package_show
|
||||
|
||||
|
@ -66,12 +65,12 @@ class AuthTest(unittest.TestCase):
|
|||
# Anonymous user (public)
|
||||
(None, None, None, False, 'active', None, None, None, None, None, True),
|
||||
# Anonymous user (private)
|
||||
(None, None, None, True, 'active', None, None, None, None, '/', False),
|
||||
(None, None, '', True, 'active', None, None, '', None, '/', False),
|
||||
(None, None, None, True, 'active', None, None, None, None, '/', True),
|
||||
(None, None, '', True, 'active', None, None, '', None, '/', True),
|
||||
# Anonymous user (private). Buy URL not shown
|
||||
(None, None, None, True, 'active', None, None, None, 'google.es', '/', False),
|
||||
(None, None, None, True, 'active', None, None, None, 'google.es', '/', True),
|
||||
# Anonymous user (private). Buy URL show
|
||||
(None, None, None, True, 'active', None, None, None, 'google.es', '/dataset/testds', False),
|
||||
(None, None, None, True, 'active', None, None, None, 'google.es', '/dataset/testds', True),
|
||||
# The creator can always see the dataset
|
||||
(1, 1, None, False, 'active', None, None, None, None, None, True),
|
||||
(1, 1, None, True, 'active', 'conwet', None, None, None, None, True),
|
||||
|
@ -79,25 +78,26 @@ class AuthTest(unittest.TestCase):
|
|||
(1, 1, None, False, 'draft', None, None, None, None, None, True),
|
||||
# Other user (no organizations)
|
||||
(1, 2, 'test', False, 'active', None, None, None, None, None, True),
|
||||
(1, 2, 'test', True, 'active', None, None, None, 'google.es', '/', False), # Buy MSG not shown
|
||||
(1, 2, 'test', True, 'active', None, None, None, None, '/dataset/testds', False), # Buy MSG not shown
|
||||
(1, 2, 'test', True, 'active', None, None, None, 'google.es', '/dataset/testds', False), # Buy MSG shown
|
||||
(1, 2, 'test', False, 'draft', None, None, None, None, None, False),
|
||||
(1, 2, 'test', True, 'active', None, None, None, 'google.es', '/', True), # Buy MSG not shown
|
||||
(1, 2, 'test', True, 'active', None, None, None, None, '/dataset/testds', True), # Buy MSG not shown
|
||||
(1, 2, 'test', True, 'active', None, None, None, 'google.es', '/dataset/testds', True), # Buy MSG shown
|
||||
(1, 2, 'test', False, 'draft', None, None, None, None, None, False),
|
||||
# Other user but authorized in the list of authorized users
|
||||
(1, 2, 'test', True, 'active', None, None, True, None, None, True),
|
||||
# Other user and not authorized in the list of authorized users
|
||||
(1, 2, 'test', True, 'active', None, None, False, 'google.es', '/', False),
|
||||
(1, 2, 'test', True, 'active', None, None, False, 'google.es', '/dataset/testds', False),
|
||||
(1, 2, 'test', True, 'active', None, None, False, 'google.es', '/', True),
|
||||
(1, 2, 'test', True, 'active', None, None, False, 'google.es', '/dataset/testds', True),
|
||||
# Other user with organizations
|
||||
(1, 2, 'test', False, 'active', 'conwet', False, None, None, None, True),
|
||||
(1, 2, 'test', True, 'active', 'conwet', False, None, None, None, False),
|
||||
(1, 2, 'test', True, 'active', 'conwet', False, None, None, None, True),
|
||||
(1, 2, 'test', True, 'active', 'conwet', True, None, None, None, True),
|
||||
(1, 2, 'test', True, 'draft', 'conwet', True, None, None, None, False),
|
||||
# Other user with organizations (user is not in the organization)
|
||||
(1, 2, 'test', True, 'active', 'conwet', False, True, None, None, True),
|
||||
(1, 2, 'test', True, 'active', 'conwet', False, False, None, None, False),
|
||||
(1, 2, 'test', True, 'active', 'conwet', False, False, 'google.es', '/dataset/testds', False),
|
||||
(1, 2, 'test', True, 'active', 'conwet', False, False, 'google.es', '/', False) ])
|
||||
(1, 2, 'test', True, 'active', 'conwet', False, False, None, None, True),
|
||||
(1, 2, 'test', True, 'active', 'conwet', False, False, 'google.es', '/dataset/testds', True),
|
||||
(1, 2, 'test', True, 'active', 'conwet', False, False, 'google.es', '/', True)
|
||||
])
|
||||
def test_auth_package_show(self, creator_user_id, user_obj_id, user, private, state, owner_org,
|
||||
owner_member, db_auth, acquire_url, request_path, authorized):
|
||||
|
||||
|
@ -123,8 +123,8 @@ class AuthTest(unittest.TestCase):
|
|||
returned_package.extras['acquire_url'] = acquire_url
|
||||
|
||||
auth.logic_auth.get_package_object = MagicMock(return_value=returned_package)
|
||||
auth.new_authz.has_user_permission_for_group_or_org = MagicMock(return_value=owner_member)
|
||||
auth.request.path = MagicMock(return_value=request_path)
|
||||
auth.authz.has_user_permission_for_group_or_org = MagicMock(return_value=owner_member)
|
||||
auth.request.path = request_path
|
||||
|
||||
# Prepare the context
|
||||
context = {'model': MagicMock()}
|
||||
|
@ -140,14 +140,14 @@ class AuthTest(unittest.TestCase):
|
|||
# Check the result
|
||||
self.assertEquals(authorized, result['success'])
|
||||
|
||||
# Premissions for organization are checked when the dataset is private, it belongs to an organization
|
||||
# Permissions for organization are checked when the dataset is private, it belongs to an organization
|
||||
# and when the dataset has not been created by the user who is asking for it
|
||||
if private and owner_org and state == 'active' and creator_user_id != user_obj_id:
|
||||
auth.new_authz.has_user_permission_for_group_or_org.assert_called_once_with(owner_org, user, 'read')
|
||||
auth.authz.has_user_permission_for_group_or_org.assert_called_once_with(owner_org, user, 'read')
|
||||
else:
|
||||
self.assertEquals(0, auth.new_authz.has_user_permission_for_group_or_org.call_count)
|
||||
self.assertEquals(0, auth.authz.has_user_permission_for_group_or_org.call_count)
|
||||
|
||||
# The databse is only initialized when:
|
||||
# The database is only initialized when:
|
||||
# * the dataset is private AND
|
||||
# * the dataset is active AND
|
||||
# * the dataset has no organization OR the user does not belong to that organization AND
|
||||
|
@ -159,10 +159,10 @@ class AuthTest(unittest.TestCase):
|
|||
self.assertEquals(0, auth.db.init_db.call_count)
|
||||
|
||||
# Conditions to buy a dataset; It should be private, active and should not belong to any organization
|
||||
if not authorized and state == 'active' and not owner_org and request_path.startswith('/dataset/'):
|
||||
auth.helpers.flash_error.assert_called_once()
|
||||
if authorized and state == 'active' and request_path and request_path.startswith('/dataset/') and acquire_url:
|
||||
auth.helpers.flash_notice.assert_called_once()
|
||||
else:
|
||||
self.assertEquals(0, auth.helpers.flash_error.call_count)
|
||||
self.assertEquals(0, auth.helpers.flash_notice.call_count)
|
||||
|
||||
@parameterized.expand([
|
||||
(None, None, None, None, None, False), # Anonymous user
|
||||
|
@ -179,7 +179,7 @@ class AuthTest(unittest.TestCase):
|
|||
returned_package.owner_org = owner_org
|
||||
|
||||
auth.logic_auth.get_package_object = MagicMock(return_value=returned_package)
|
||||
auth.new_authz.has_user_permission_for_group_or_org = MagicMock(return_value=owner_member)
|
||||
auth.authz.has_user_permission_for_group_or_org = MagicMock(return_value=owner_member)
|
||||
|
||||
# Prepare the context
|
||||
context = {}
|
||||
|
@ -198,59 +198,107 @@ class AuthTest(unittest.TestCase):
|
|||
# Permissions for organization are checked when the user asking to update the dataset is not the creator
|
||||
# and when the dataset has organization
|
||||
if creator_user_id != user_obj_id and owner_org:
|
||||
auth.new_authz.has_user_permission_for_group_or_org.assert_called_once_with(owner_org, user, 'update_dataset')
|
||||
auth.authz.has_user_permission_for_group_or_org.assert_called_once_with(owner_org, user, 'update_dataset')
|
||||
else:
|
||||
self.assertEquals(0, auth.new_authz.has_user_permission_for_group_or_org.call_count)
|
||||
self.assertEquals(0, auth.authz.has_user_permission_for_group_or_org.call_count)
|
||||
|
||||
@parameterized.expand([
|
||||
(True, True),
|
||||
(True, False),
|
||||
(False, False),
|
||||
(False, False)
|
||||
# if package dont exist
|
||||
(False, None, None, None, False, 'active', None, None, None, False),
|
||||
# Anonymous user only can view resources of a public and active package
|
||||
(True, None, None, None, False, 'active', None, None, None, True),
|
||||
(True, None, None, None, True, 'active', None, None, None, False),
|
||||
(True, None, None, '', True, 'active', None, None, None, False),
|
||||
# The creator can always see the resource
|
||||
(True, 1, 1, None, False, 'active', None, None, None, True),
|
||||
(True, 1, 1, None, True, 'active', None, None, None, True),
|
||||
(True, 1, 1, None, True, 'draft', None, None, None, True),
|
||||
(True, 1, 1, None, False, 'draft', None, None, None, True),
|
||||
# Other user (no organizations)
|
||||
(True, 1, 2, 'test', False, 'active', None, None, None, True),
|
||||
(True, 1, 2, 'test', True, 'active', None, None, None, False),
|
||||
(True, 1, 2, 'test', True, 'draft', None, None, None, False),
|
||||
# Other user but authorized in the list of authorized users
|
||||
(True, 1, 2, 'test', True, 'active', None, None, True, True),
|
||||
# Other user and not authorized in the list of authorized users
|
||||
(True, 1, 2, 'test', True, 'active', None, None, False, False),
|
||||
# Other user with organizations
|
||||
(True, 1, 2, 'test', True, 'active', 'conwet', False, None, False),
|
||||
(True, 1, 2, 'test', True, 'active', 'conwet', True, None, True),
|
||||
|
||||
])
|
||||
def test_auth_resource_show(self, exist_pkg=True, authorized_pkg=True):
|
||||
def test_auth_resource_show(self, exist_pkg, creator_user_id, user_obj_id, user, private, state, owner_org,
|
||||
owner_member, db_auth, authorized):
|
||||
#Recover the exception
|
||||
auth.tk.ObjectNotFound = self._tk.ObjectNotFound
|
||||
|
||||
# Mock the calls
|
||||
package = MagicMock()
|
||||
package.id = '1'
|
||||
# Configure the mocks
|
||||
if exist_pkg:
|
||||
returned_package = MagicMock()
|
||||
returned_package.creator_user_id = creator_user_id
|
||||
returned_package.private = private
|
||||
returned_package.state = state
|
||||
returned_package.owner_org = owner_org
|
||||
returned_package.extras = {}
|
||||
else:
|
||||
returned_package = None
|
||||
|
||||
final_query = MagicMock()
|
||||
final_query.first = MagicMock(return_value=package if exist_pkg else None)
|
||||
returned_resource = MagicMock()
|
||||
returned_resource.package_id = 1
|
||||
|
||||
second_join = MagicMock()
|
||||
second_join.filter = MagicMock(return_value=final_query)
|
||||
# Configure the database
|
||||
db_response = []
|
||||
if db_auth is True:
|
||||
out = auth.db.AllowedUser()
|
||||
out.package_id = 'package_id'
|
||||
out.user_name = user
|
||||
db_response.append(out)
|
||||
|
||||
first_join = MagicMock()
|
||||
first_join.join = MagicMock(return_value=second_join)
|
||||
# Prepare the context
|
||||
context = {'model': MagicMock()}
|
||||
if user is not None:
|
||||
context['user'] = user
|
||||
if user_obj_id is not None:
|
||||
context['auth_user_obj'] = MagicMock()
|
||||
context['auth_user_obj'].id = user_obj_id
|
||||
|
||||
query = MagicMock()
|
||||
query.join = MagicMock(return_value=first_join)
|
||||
auth.db.AllowedUser.get = MagicMock(return_value=db_response)
|
||||
auth.logic_auth.get_resource_object = MagicMock(return_value=returned_resource)
|
||||
auth.logic_auth.get_package_object = MagicMock(return_value=returned_package)
|
||||
auth.authz.has_user_permission_for_group_or_org = MagicMock(return_value=owner_member)
|
||||
|
||||
model = MagicMock()
|
||||
session = MagicMock()
|
||||
session.query = MagicMock(return_value=query)
|
||||
model.Session = session
|
||||
|
||||
# Create the context
|
||||
context = {}
|
||||
context['model'] = model
|
||||
|
||||
# Mock the package_show function
|
||||
self._package_show = auth.package_show
|
||||
success = True if authorized_pkg else False
|
||||
auth.package_show = MagicMock(return_value={'success': success})
|
||||
# Prepare the context
|
||||
context = {'model': MagicMock()}
|
||||
if user is not None:
|
||||
context['user'] = user
|
||||
if user_obj_id is not None:
|
||||
context['auth_user_obj'] = MagicMock()
|
||||
context['auth_user_obj'].id = user_obj_id
|
||||
|
||||
if not exist_pkg:
|
||||
self.assertRaises(self._tk.ObjectNotFound, auth.resource_show, context, {})
|
||||
else:
|
||||
result = auth.resource_show(context, {})
|
||||
self.assertEquals(authorized_pkg, result['success'])
|
||||
self.assertEquals(authorized, result['success'])
|
||||
|
||||
if private and owner_org and state == 'active' and creator_user_id != user_obj_id:
|
||||
auth.authz.has_user_permission_for_group_or_org.assert_called_once_with(owner_org, user, 'read')
|
||||
else:
|
||||
self.assertEquals(0, auth.authz.has_user_permission_for_group_or_org.call_count)
|
||||
|
||||
if private and state == 'active' and (not owner_org or not owner_member) and (creator_user_id != user_obj_id or user_obj_id is None):
|
||||
# Check that the database has been initialized properly
|
||||
auth.db.init_db.assert_called_once_with(context['model'])
|
||||
else:
|
||||
self.assertEquals(0, auth.db.init_db.call_count)
|
||||
|
||||
|
||||
def test_package_acquired(self):
|
||||
self.assertTrue(auth.package_acquired({}, {})['success'])
|
||||
|
||||
def test_package_deleted(self):
|
||||
self.assertTrue(auth.revoke_access({}, {})['success'])
|
||||
|
||||
@parameterized.expand([
|
||||
({'user': 'user_1'}, {'user': 'user_1'}, True),
|
||||
({'user': 'user_2'}, {'user': 'user_1'}, False),
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ckanext.privatedatasets.controllers.ui_controller as controller
|
||||
import unittest
|
||||
|
||||
from mock import MagicMock, ANY
|
||||
from nose_parameterized import parameterized
|
||||
|
||||
|
||||
class UIControllerTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
# Get the instance
|
||||
self.instanceUI = controller.AcquiredDatasetsControllerUI()
|
||||
|
||||
# Load the mocks
|
||||
self._plugins = controller.plugins
|
||||
controller.plugins = MagicMock()
|
||||
|
||||
self._model = controller.model
|
||||
controller.model = MagicMock()
|
||||
|
||||
# Set exceptions
|
||||
controller.plugins.toolkit.ObjectNotFound = self._plugins.toolkit.ObjectNotFound
|
||||
controller.plugins.toolkit.NotAuthorized = self._plugins.toolkit.NotAuthorized
|
||||
|
||||
def tearDown(self):
|
||||
# Unmock
|
||||
controller.plugins = self._plugins
|
||||
controller.model = self._model
|
||||
|
||||
@parameterized.expand([
|
||||
(controller.plugins.toolkit.ObjectNotFound, 404),
|
||||
(controller.plugins.toolkit.NotAuthorized, 401)
|
||||
])
|
||||
def test_exceptions_loading_users(self, exception, expected_status):
|
||||
|
||||
# Configure the mock
|
||||
user_show = MagicMock(side_effect=exception)
|
||||
controller.plugins.toolkit.get_action = MagicMock(return_value=user_show)
|
||||
|
||||
# Call the function
|
||||
self.instanceUI.user_acquired_datasets()
|
||||
|
||||
# Assertations
|
||||
expected_context = {
|
||||
'model': controller.model,
|
||||
'session': controller.model.Session,
|
||||
'user': controller.plugins.toolkit.c.user
|
||||
}
|
||||
|
||||
user_show.assert_called_once_with(expected_context, {'user_obj': controller.plugins.toolkit.c.userobj})
|
||||
controller.plugins.toolkit.abort.assert_called_once_with(expected_status, ANY)
|
||||
|
||||
def test_no_error_loading_users(self):
|
||||
|
||||
user = 'example_user_test'
|
||||
controller.plugins.toolkit.c.user = user
|
||||
|
||||
# actions
|
||||
default_user = {'user_name': 'test', 'another_val': 'example value'}
|
||||
user_show = MagicMock(return_value=default_user)
|
||||
acquisitions_list = MagicMock()
|
||||
def _get_action(action):
|
||||
if action == 'user_show':
|
||||
return user_show
|
||||
else:
|
||||
return acquisitions_list
|
||||
|
||||
controller.plugins.toolkit.get_action = MagicMock(side_effect=_get_action)
|
||||
|
||||
# Call the function
|
||||
returned = self.instanceUI.user_acquired_datasets()
|
||||
|
||||
# User_show called correctly
|
||||
expected_context = {
|
||||
'model': controller.model,
|
||||
'session': controller.model.Session,
|
||||
'user': controller.plugins.toolkit.c.user
|
||||
}
|
||||
|
||||
user_show.assert_called_once_with(expected_context, {'user_obj': controller.plugins.toolkit.c.userobj})
|
||||
|
||||
# Query called correctry
|
||||
expected_user = default_user.copy()
|
||||
expected_user['acquired_datasets'] = acquisitions_list.return_value
|
||||
acquisitions_list.assert_called_with(expected_context, None)
|
||||
self.assertEquals(expected_user, controller.plugins.toolkit.c.user_dict)
|
||||
|
||||
# Check that the render method has been called and that its result has been returned
|
||||
self.assertEquals(controller.plugins.toolkit.render.return_value, returned)
|
||||
controller.plugins.toolkit.render.assert_called_once_with('user/dashboard_acquired.html')
|
|
@ -21,7 +21,7 @@ import unittest
|
|||
import ckanext.privatedatasets.converters_validators as conv_val
|
||||
|
||||
from mock import MagicMock
|
||||
from nose_parameterized import parameterized
|
||||
from parameterized import parameterized
|
||||
|
||||
|
||||
class ConvertersValidatorsTest(unittest.TestCase):
|
||||
|
@ -127,10 +127,6 @@ class ConvertersValidatorsTest(unittest.TestCase):
|
|||
def test_allowed_user_convert(self, users, previous_users, expected_users):
|
||||
key_str = 'allowed_users_str'
|
||||
key = 'allowed_users'
|
||||
|
||||
# Configure mock
|
||||
name_validator = MagicMock()
|
||||
conv_val.toolkit.get_validator = MagicMock(return_value=name_validator)
|
||||
|
||||
# Fullfill the data dictionary
|
||||
# * list should be included in the allowed_users filed
|
||||
|
@ -151,7 +147,6 @@ class ConvertersValidatorsTest(unittest.TestCase):
|
|||
|
||||
# Check that the users are set properly
|
||||
for i in range(previous_users, previous_users + len(expected_users)):
|
||||
name_validator.assert_any_call(expected_users[i - previous_users], context)
|
||||
self.assertEquals(expected_users[i - previous_users], data[(key, i)])
|
||||
|
||||
@parameterized.expand([
|
||||
|
|
|
@ -21,7 +21,7 @@ import unittest
|
|||
import ckanext.privatedatasets.parsers.fiware as fiware
|
||||
|
||||
from mock import MagicMock
|
||||
from nose_parameterized import parameterized
|
||||
from parameterized import parameterized
|
||||
|
||||
|
||||
TEST_CASES = {
|
||||
|
@ -39,26 +39,26 @@ TEST_CASES = {
|
|||
'error': {
|
||||
'host': 'localhost',
|
||||
'json': {"customer_name": "test", "resources": [{"url": "http://localhosta/dataset/ds1"}]},
|
||||
'error': 'Dataset ds1 is associated with the CKAN instance located at localhosta',
|
||||
'error': 'Dataset ds1 is associated with the CKAN instance located at localhosta, expected localhost',
|
||||
},
|
||||
'error_one_ds': {
|
||||
'host': 'localhost',
|
||||
'json': {"customer_name": "test", "resources": [{"url": "http://localhosta/dataset/ds1"},
|
||||
{"url": "http://localhost/dataset/ds2"}]},
|
||||
'error': 'Dataset ds1 is associated with the CKAN instance located at localhosta',
|
||||
'error': 'Dataset ds1 is associated with the CKAN instance located at localhosta, expected localhost',
|
||||
},
|
||||
'two_errors': {
|
||||
'host': 'localhost',
|
||||
'json': {"customer_name": "test", "resources": [{"url": "http://localhosta/dataset/ds1"},
|
||||
{"url": "http://localhostb/dataset/ds2"}]},
|
||||
'error': 'Dataset ds1 is associated with the CKAN instance located at localhosta',
|
||||
'error': 'Dataset ds1 is associated with the CKAN instance located at localhosta, expected localhost',
|
||||
},
|
||||
'two_errors_two_ds': {
|
||||
'host': 'example.com',
|
||||
'json': {"customer_name": "test", "resources": [{"url": "http://localhosta/dataset/ds1"},
|
||||
{"url": "http://example.es/dataset/ds2"}, {"url": "http://example.com/dataset/ds3"},
|
||||
{"url": "http://example.com/dataset/ds4"}]},
|
||||
'error': 'Dataset ds1 is associated with the CKAN instance located at localhosta',
|
||||
'error': 'Dataset ds1 is associated with the CKAN instance located at localhosta, expected example.com',
|
||||
},
|
||||
'no_customer_name': {
|
||||
'host': 'localhost',
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 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.
|
||||
|
||||
|
@ -20,8 +21,8 @@
|
|||
import unittest
|
||||
import ckanext.privatedatasets.helpers as helpers
|
||||
|
||||
from mock import MagicMock
|
||||
from nose_parameterized import parameterized
|
||||
from mock import patch, MagicMock
|
||||
from parameterized import parameterized
|
||||
|
||||
|
||||
class HelpersTest(unittest.TestCase):
|
||||
|
@ -33,13 +34,11 @@ class HelpersTest(unittest.TestCase):
|
|||
|
||||
self._tk = helpers.tk
|
||||
helpers.tk = MagicMock()
|
||||
helpers.tk.config = {}
|
||||
|
||||
self._db = helpers.db
|
||||
helpers.db = MagicMock()
|
||||
|
||||
self._config = helpers.config
|
||||
helpers.config = {}
|
||||
|
||||
self._request = helpers.request
|
||||
helpers.request = MagicMock()
|
||||
|
||||
|
@ -47,7 +46,6 @@ class HelpersTest(unittest.TestCase):
|
|||
helpers.model = self._model
|
||||
helpers.tk = self._tk
|
||||
helpers.db = self._db
|
||||
helpers.config = self._config
|
||||
helpers.request = self._request
|
||||
|
||||
@parameterized.expand([
|
||||
|
@ -129,25 +127,59 @@ class HelpersTest(unittest.TestCase):
|
|||
helpers.tk.check_access.assert_called_once_with('package_show', context, package)
|
||||
|
||||
@parameterized.expand([
|
||||
(None, False),
|
||||
('True', True),
|
||||
('False', False)
|
||||
(None, False, None),
|
||||
('True', True, None),
|
||||
('False', False, None),
|
||||
('afa ', False, None),
|
||||
(True, True, None),
|
||||
(False, False, None),
|
||||
(False, True , 'true'),
|
||||
(False, True , 'on'),
|
||||
(False, True , '1'),
|
||||
(True, False, '0'),
|
||||
(True, False, 'off'),
|
||||
(True, False, 'fAlsE'),
|
||||
(True, False, 'fAlsE'),
|
||||
])
|
||||
def test_show_acquire_url_on_create(self, config_value, expected_value):
|
||||
@patch("ckanext.privatedatasets.helpers.os.environ", new={})
|
||||
def test_show_acquire_url_on_create(self, config_value, expected_value, env_val):
|
||||
# {} is shared between tests, so we have clear it each time
|
||||
helpers.os.environ.clear()
|
||||
|
||||
if config_value is not None:
|
||||
helpers.config['ckan.privatedatasets.show_acquire_url_on_create'] = config_value
|
||||
helpers.tk.config['ckan.privatedatasets.show_acquire_url_on_create'] = config_value
|
||||
|
||||
if env_val:
|
||||
helpers.os.environ['CKAN_PRIVATEDATASETS_SHOW_ACQUIRE_URL_ON_CREATE'] = env_val
|
||||
|
||||
# Call the function
|
||||
self.assertEquals(expected_value, helpers.show_acquire_url_on_create())
|
||||
|
||||
@parameterized.expand([
|
||||
(None, False),
|
||||
('True', True),
|
||||
('False', False)
|
||||
(None, False, None),
|
||||
('True', True, None),
|
||||
(' tRUe', True, None),
|
||||
('False', False, None),
|
||||
(True, True, None),
|
||||
(False, False, None),
|
||||
(False, True , 'trUe'),
|
||||
(False, True , 'on'),
|
||||
(False, True , '1'),
|
||||
(True, False, '0'),
|
||||
(True, False, 'off'),
|
||||
(True, False, 'fAlsE'),
|
||||
(True, False, 'potato'),
|
||||
])
|
||||
def test_show_acquire_url_on_edit(self, config_value, expected_value):
|
||||
@patch("ckanext.privatedatasets.helpers.os.environ", new={})
|
||||
def test_show_acquire_url_on_edit(self, config_value, expected_value, env_val):
|
||||
# {} is shared between tests, so we have clear it each time
|
||||
helpers.os.environ.clear()
|
||||
|
||||
if config_value is not None:
|
||||
helpers.config['ckan.privatedatasets.show_acquire_url_on_edit'] = config_value
|
||||
helpers.tk.config['ckan.privatedatasets.show_acquire_url_on_edit'] = config_value
|
||||
|
||||
if env_val:
|
||||
helpers.os.environ['CKAN_PRIVATEDATASETS_SHOW_ACQUIRE_URL_ON_EDIT'] = env_val
|
||||
|
||||
# Call the function
|
||||
self.assertEquals(expected_value, helpers.show_acquire_url_on_edit())
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||
# Copyright (c) 2014 - 2017 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||
|
||||
# This file is part of CKAN Private Dataset Extension.
|
||||
|
||||
|
@ -19,10 +19,12 @@
|
|||
|
||||
import unittest
|
||||
import copy
|
||||
import ckanext.privatedatasets.plugin as plugin
|
||||
|
||||
from flask import Blueprint
|
||||
from mock import MagicMock
|
||||
from nose_parameterized import parameterized
|
||||
from parameterized import parameterized
|
||||
|
||||
import ckanext.privatedatasets.plugin as plugin
|
||||
|
||||
|
||||
class PluginTest(unittest.TestCase):
|
||||
|
@ -51,7 +53,7 @@ class PluginTest(unittest.TestCase):
|
|||
(plugin.p.IDatasetForm,),
|
||||
(plugin.p.IAuthFunctions,),
|
||||
(plugin.p.IConfigurer,),
|
||||
(plugin.p.IRoutes,),
|
||||
(plugin.p.IBlueprint,),
|
||||
(plugin.p.IActions,),
|
||||
(plugin.p.IPackageController,),
|
||||
(plugin.p.ITemplateHelpers,)
|
||||
|
@ -63,18 +65,13 @@ class PluginTest(unittest.TestCase):
|
|||
('package_show', plugin.auth.package_show),
|
||||
('package_update', plugin.auth.package_update),
|
||||
('resource_show', plugin.auth.resource_show),
|
||||
('resource_show', plugin.auth.resource_show, True, False),
|
||||
('package_acquired', plugin.auth.package_acquired),
|
||||
('acquisitions_list', plugin.auth.acquisitions_list)
|
||||
('acquisitions_list', plugin.auth.acquisitions_list),
|
||||
('revoke_access', plugin.auth.revoke_access)
|
||||
])
|
||||
def test_auth_function(self, function_name, expected_function, is_ckan_23=False, expected=True):
|
||||
plugin.tk.check_ckan_version = MagicMock(return_value=is_ckan_23)
|
||||
def test_auth_function(self, function_name, expected_function):
|
||||
auth_functions = self.privateDatasets.get_auth_functions()
|
||||
|
||||
if expected:
|
||||
self.assertEquals(auth_functions[function_name], expected_function)
|
||||
else:
|
||||
self.assertNotIn(function_name, auth_functions)
|
||||
self.assertEquals(auth_functions[function_name], expected_function)
|
||||
|
||||
def test_update_config(self):
|
||||
# Call the method
|
||||
|
@ -82,22 +79,20 @@ class PluginTest(unittest.TestCase):
|
|||
self.privateDatasets.update_config(config)
|
||||
|
||||
# Test that functions are called as expected
|
||||
plugin.tk.add_template_directory.assert_called_once_with(config, 'templates')
|
||||
if self._tk.check_ckan_version(min_version='2.8'):
|
||||
plugin.tk.add_template_directory.assert_called_once_with(config, 'templates_2.8')
|
||||
else:
|
||||
plugin.tk.add_template_directory.assert_called_once_with(config, 'templates')
|
||||
plugin.tk.add_resource('fanstatic', 'privatedatasets')
|
||||
|
||||
def test_map(self):
|
||||
def test_get_blueprint(self):
|
||||
# Call the method
|
||||
m = MagicMock()
|
||||
self.privateDatasets.before_map(m)
|
||||
|
||||
# Test that the connect method has been called
|
||||
m.connect.assert_any_call('user_acquired_datasets', '/dashboard/acquired', ckan_icon='shopping-cart',
|
||||
controller='ckanext.privatedatasets.controllers.ui_controller:AcquiredDatasetsControllerUI',
|
||||
action='user_acquired_datasets', conditions=dict(method=['GET']))
|
||||
self.assertIsInstance(self.privateDatasets.get_blueprint(), Blueprint)
|
||||
|
||||
@parameterized.expand([
|
||||
('package_acquired', plugin.actions.package_acquired),
|
||||
('acquisitions_list', plugin.actions.acquisitions_list)
|
||||
('acquisitions_list', plugin.actions.acquisitions_list),
|
||||
('revoke_access', plugin.actions.revoke_access)
|
||||
])
|
||||
def test_actions_function(self, function_name, expected_function):
|
||||
actions = self.privateDatasets.get_actions()
|
||||
|
@ -214,41 +209,73 @@ class PluginTest(unittest.TestCase):
|
|||
self.assertTrue(found)
|
||||
|
||||
@parameterized.expand([
|
||||
(True, 1, 1, False, True, True),
|
||||
(True, 1, 2, False, True, True),
|
||||
(True, 1, 1, True, True, True),
|
||||
(True, 1, 2, True, True, True),
|
||||
(True, 1, None, None, True, True),
|
||||
(True, 1, 1, None, True, True),
|
||||
(True, 1, None, True, True, True),
|
||||
(True, 1, None, False, True, True),
|
||||
(False, 1, 1, False, True, True),
|
||||
(False, 1, 2, False, True, False),
|
||||
(False, 1, 1, True, True, True),
|
||||
(False, 1, 2, True, True, True),
|
||||
(False, 1, None, None, True, False),
|
||||
(False, 1, 1, None, True, True),
|
||||
(False, 1, None, True, True, True),
|
||||
(False, 1, None, False, True, False),
|
||||
(True, 1, 1, False, False, False),
|
||||
(True, 1, 2, False, False, False),
|
||||
(True, 1, 1, True, False, False),
|
||||
(True, 1, 2, True, False, False),
|
||||
(True, 1, None, None, False, False),
|
||||
(True, 1, 1, None, False, False),
|
||||
(True, 1, None, True, False, False),
|
||||
(True, 1, None, False, False, False),
|
||||
(False, 1, 1, False, False, False),
|
||||
(False, 1, 2, False, False, False),
|
||||
(False, 1, 1, True, False, False),
|
||||
(False, 1, 2, True, False, False),
|
||||
(False, 1, None, None, False, False),
|
||||
(False, 1, 1, None, False, False),
|
||||
(False, 1, None, True, False, False),
|
||||
(False, 1, None, False, False, False),
|
||||
(True, 1, 1, False, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, 2, False, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, 1, True, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, 2, True, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, None, None, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, 1, None, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, None, True, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, None, False, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, 1, False, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, 2, False, True, False, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, 1, True, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, 2, True, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, None, None, True, False, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, 1, None, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, None, True, True, True, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, None, False, True, False, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, 1, False, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, 2, False, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, 1, True, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, 2, True, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, None, None, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, 1, None, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, None, True, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, None, False, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, 1, False, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, 2, False, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, 1, True, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, 2, True, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, None, None, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, 1, None, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, None, True, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(False, 1, None, False, False, False, [{'id': 1}, {'id': 2}], True),
|
||||
(True, 1, 1, False, True, True, [{}, {}], False),
|
||||
(True, 1, 2, False, True, True, [{}, {}], False),
|
||||
(True, 1, 1, True, True, True, [{}, {}], False),
|
||||
(True, 1, 2, True, True, True, [{}, {}], False),
|
||||
(True, 1, None, None, True, True, [{}, {}], False),
|
||||
(True, 1, 1, None, True, True, [{}, {}], False),
|
||||
(True, 1, None, True, True, True, [{}, {}], False),
|
||||
(True, 1, None, False, True, True, [{}, {}], False),
|
||||
(False, 1, 1, False, True, True, [{}, {}], False),
|
||||
(False, 1, 2, False, True, False, [{}, {}], False),
|
||||
(False, 1, 1, True, True, True, [{}, {}], False),
|
||||
(False, 1, 2, True, True, True, [{}, {}], False),
|
||||
(False, 1, None, None, True, False, [{}, {}], False),
|
||||
(False, 1, 1, None, True, True, [{}, {}], False),
|
||||
(False, 1, None, True, True, True, [{}, {}], False),
|
||||
(False, 1, None, False, True, False, [{}, {}], False),
|
||||
(True, 1, 1, False, False, False, [{}, {}], False),
|
||||
(True, 1, 2, False, False, False, [{}, {}], False),
|
||||
(True, 1, 1, True, False, False, [{}, {}], False),
|
||||
(True, 1, 2, True, False, False, [{}, {}], False),
|
||||
(True, 1, None, None, False, False, [{}, {}], False),
|
||||
(True, 1, 1, None, False, False, [{}, {}], False),
|
||||
(True, 1, None, True, False, False, [{}, {}], False),
|
||||
(True, 1, None, False, False, False, [{}, {}], False),
|
||||
(False, 1, 1, False, False, False, [{}, {}], False),
|
||||
(False, 1, 2, False, False, False, [{}, {}], False),
|
||||
(False, 1, 1, True, False, False, [{}, {}], False),
|
||||
(False, 1, 2, True, False, False, [{}, {}], False),
|
||||
(False, 1, None, None, False, False, [{}, {}], False),
|
||||
(False, 1, 1, None, False, False, [{}, {}], False),
|
||||
(False, 1, None, True, False, False, [{}, {}], False),
|
||||
(False, 1, None, False, False, False, [{}, {}], False),
|
||||
])
|
||||
def test_packagecontroller_after_show(self, update_via_api, creator_id, user_id, sysadmin, private, fields_expected):
|
||||
|
||||
def test_packagecontroller_after_show(self, update_via_api, creator_id, user_id, sysadmin, private, fields_expected, resources, resources_fields):
|
||||
|
||||
context = {'updating_via_cb': update_via_api}
|
||||
|
||||
if creator_id is not None or sysadmin is not None:
|
||||
|
@ -257,7 +284,7 @@ class PluginTest(unittest.TestCase):
|
|||
user.sysadmin = sysadmin
|
||||
context['auth_user_obj'] = user
|
||||
|
||||
pkg_dict = {'creator_user_id': creator_id, 'allowed_users': ['a', 'b', 'c'], 'searchable': True, 'acquire_url': 'http://google.es', 'private': private}
|
||||
pkg_dict = {'creator_user_id': creator_id, 'allowed_users': ['a', 'b', 'c'], 'searchable': True, 'acquire_url': 'http://google.es', 'private': private, 'resources': resources, 'num_resources': 2}
|
||||
|
||||
# Call the function
|
||||
result = self.privateDatasets.after_show(context, pkg_dict) # Call the function
|
||||
|
@ -270,6 +297,14 @@ class PluginTest(unittest.TestCase):
|
|||
else:
|
||||
self.assertFalse(field in result)
|
||||
|
||||
fields = ['resources', 'num_resources']
|
||||
for field in fields:
|
||||
if resources_fields:
|
||||
self.assertTrue(field in result)
|
||||
else:
|
||||
self.assertFalse(field in result)
|
||||
|
||||
|
||||
@parameterized.expand([
|
||||
('public', None, 'public'),
|
||||
('public', 'False', 'private'),
|
||||
|
@ -292,11 +327,20 @@ class PluginTest(unittest.TestCase):
|
|||
package_id = 'package_id'
|
||||
|
||||
# Configure mocks
|
||||
default_dict = {'a': '0', 'b': 1, 'm': True, 'revision_timestamp': '2000'}
|
||||
revision = {'timestamp': '7888'}
|
||||
default_dict = {'a': '0', 'b': 1, 'm': True, 'revision_id': 'revision_id_uuidv4'}
|
||||
expected_dict = default_dict.copy()
|
||||
expected_dict['metadata_modified'] = default_dict['revision_timestamp']
|
||||
expected_dict['metadata_modified'] = revision['timestamp']
|
||||
package_show = MagicMock(return_value=default_dict.copy())
|
||||
plugin.tk.get_action = MagicMock(return_value=package_show)
|
||||
revision_show = MagicMock(return_value=revision.copy())
|
||||
|
||||
def _get_action(action):
|
||||
if action == 'package_show':
|
||||
return package_show
|
||||
elif action == 'revision_show':
|
||||
return revision_show
|
||||
|
||||
plugin.tk.get_action = MagicMock(side_effect=_get_action)
|
||||
|
||||
# Each time 'AllowedUser' is called, we must get a new instance
|
||||
# and this is the way to get this behaviour
|
||||
|
@ -439,3 +483,40 @@ class PluginTest(unittest.TestCase):
|
|||
|
||||
self.assertEquals(final_search_results['facets'], search_results['facets'])
|
||||
self.assertEquals(final_search_results['elements'], search_results['elements'])
|
||||
|
||||
@parameterized.expand([
|
||||
(True,),
|
||||
(False,)
|
||||
])
|
||||
def test_package_controller_before_view(self, user_allowed):
|
||||
|
||||
pkg_dict = {'resources': [{'id': 1}, {'id': 2}, {'id': 3}]}
|
||||
pkg_dict_not_allowed = {'resources': []}
|
||||
|
||||
plugin.tk.check_access.side_effect = None if user_allowed else plugin.tk.NotAuthorized
|
||||
|
||||
result = self.privateDatasets.before_view(pkg_dict)
|
||||
|
||||
if user_allowed:
|
||||
self.assertEquals(result['resources'], pkg_dict['resources'])
|
||||
else:
|
||||
self.assertEquals(result['resources'], pkg_dict_not_allowed['resources'])
|
||||
|
||||
@parameterized.expand([
|
||||
(True,),
|
||||
(False,)
|
||||
])
|
||||
def test_resource_controller_before_show(self, user_allowed):
|
||||
|
||||
resource_dict = {'id': 1, 'resource_name': 'resource_test'}
|
||||
|
||||
plugin.tk.check_access.side_effect = None if user_allowed else plugin.tk.NotAuthorized
|
||||
|
||||
result = self.privateDatasets.before_show(resource_dict)
|
||||
|
||||
if user_allowed:
|
||||
self.assertEquals(result['id'], resource_dict['id'])
|
||||
self.assertEquals(result['resource_name'], resource_dict['resource_name'])
|
||||
else:
|
||||
self.assertNotIn('id', result)
|
||||
self.assertNotIn('resource_name', result)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid
|
||||
# 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.
|
||||
|
||||
|
@ -17,21 +18,28 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with CKAN Private Dataset Extension. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from nose_parameterized import parameterized
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.support.ui import Select
|
||||
from subprocess import Popen
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
import ckan.lib.search.index as search_index
|
||||
import ckan.model as model
|
||||
import ckanext.privatedatasets.db as db
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
import re
|
||||
import requests
|
||||
from subprocess import Popen
|
||||
import time
|
||||
|
||||
import ckan.lib.search.index as search_index
|
||||
import ckan.model as model
|
||||
from parameterized import parameterized
|
||||
import requests
|
||||
from selenium import webdriver
|
||||
from selenium.common.exceptions import NoAlertPresentException
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.support.ui import Select, WebDriverWait
|
||||
|
||||
import ckanext.privatedatasets.db as db
|
||||
|
||||
|
||||
def get_dataset_url(dataset_name):
|
||||
return dataset_name.replace(' ', '-').lower()
|
||||
|
@ -41,14 +49,20 @@ class TestSelenium(unittest.TestCase):
|
|||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# Run CKAN
|
||||
env = os.environ.copy()
|
||||
env['DEBUG'] = 'True'
|
||||
env['OAUTHLIB_INSECURE_TRANSPORT'] = 'True'
|
||||
env['DEBUG'] = 'False'
|
||||
cls._process = Popen(['paster', 'serve', 'test.ini'], env=env)
|
||||
|
||||
# Init Selenium
|
||||
cls.driver = webdriver.Firefox()
|
||||
cls.base_url = 'http://localhost:5000/'
|
||||
cls.driver.set_window_size(1024, 768)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls._process.terminate()
|
||||
cls.driver.quit()
|
||||
|
||||
def clearBBDD(self):
|
||||
# Clean Solr
|
||||
|
@ -67,55 +81,68 @@ class TestSelenium(unittest.TestCase):
|
|||
def setUp(self):
|
||||
self.clearBBDD()
|
||||
|
||||
self.driver = webdriver.Firefox()
|
||||
self.driver.implicitly_wait(5)
|
||||
self.driver.set_window_size(1024, 768)
|
||||
self.base_url = 'http://127.0.0.1:5000/'
|
||||
|
||||
def tearDown(self):
|
||||
self.driver.get(self.base_url)
|
||||
try: # pragma: no cover
|
||||
# Accept any "Are you sure to leave?" alert
|
||||
self.driver.switch_to.alert.accept()
|
||||
self.driver.switch_to.default_content()
|
||||
except NoAlertPresentException:
|
||||
pass
|
||||
WebDriverWait(self.driver, 10).until(lambda driver: self.base_url == driver.current_url)
|
||||
self.driver.delete_all_cookies()
|
||||
self.clearBBDD()
|
||||
self.driver.quit()
|
||||
|
||||
def assert_fields_disabled(self, fields):
|
||||
for field in fields:
|
||||
self.assertFalse(self.driver.find_element_by_id(field).is_enabled())
|
||||
|
||||
def logout(self):
|
||||
self.driver.find_element_by_css_selector('i.icon-signout').click()
|
||||
self.driver.delete_all_cookies()
|
||||
self.driver.get(self.base_url)
|
||||
|
||||
def register(self, username, fullname, mail, password):
|
||||
def register(self, username, fullname, mail):
|
||||
driver = self.driver
|
||||
driver.get(self.base_url)
|
||||
driver.find_element_by_link_text('Register').click()
|
||||
driver.find_element_by_id('field-username').clear()
|
||||
WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, "field-username"))).clear()
|
||||
driver.find_element_by_id('field-username').send_keys(username)
|
||||
driver.find_element_by_id('field-fullname').clear()
|
||||
driver.find_element_by_id('field-fullname').send_keys(fullname)
|
||||
driver.find_element_by_id('field-email').clear()
|
||||
driver.find_element_by_id('field-email').send_keys(mail)
|
||||
driver.find_element_by_id('field-password').clear()
|
||||
driver.find_element_by_id('field-password').send_keys(password)
|
||||
driver.find_element_by_id('field-password').send_keys("1234" + username)
|
||||
driver.find_element_by_id('field-confirm-password').clear()
|
||||
driver.find_element_by_id('field-confirm-password').send_keys(password)
|
||||
driver.find_element_by_id('field-confirm-password').send_keys("1234" + username)
|
||||
driver.find_element_by_name('save').click()
|
||||
self.logout()
|
||||
|
||||
def login(self, username, password):
|
||||
def login(self, username):
|
||||
driver = self.driver
|
||||
driver.get(self.base_url)
|
||||
driver.find_element_by_link_text('Log in').click()
|
||||
driver.find_element_by_id('field-login').clear()
|
||||
login_btn = WebDriverWait(driver, 15).until(
|
||||
EC.element_to_be_clickable((By.LINK_TEXT, 'Log in'))
|
||||
)
|
||||
login_btn.click()
|
||||
|
||||
WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, "field-login"))).clear()
|
||||
driver.find_element_by_id('field-login').send_keys(username)
|
||||
driver.find_element_by_id('field-password').clear()
|
||||
driver.find_element_by_id('field-password').send_keys(password)
|
||||
driver.find_element_by_id('field-password').send_keys("1234" + username)
|
||||
driver.find_element_by_id('field-remember').click()
|
||||
driver.find_element_by_css_selector('button.btn.btn-primary').click()
|
||||
|
||||
def create_organization(self, name, description, users):
|
||||
driver = self.driver
|
||||
driver.get(self.base_url)
|
||||
driver.find_element_by_link_text('Organizations').click()
|
||||
driver.find_element_by_link_text('Add Organization').click()
|
||||
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Organizations'))).click()
|
||||
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Add Organization'))).click()
|
||||
WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, 'field-name')))
|
||||
|
||||
# Wait a bit to let ckan add javascript hooks
|
||||
time.sleep(0.2)
|
||||
|
||||
driver.find_element_by_id('field-name').clear()
|
||||
driver.find_element_by_id('field-name').send_keys(name)
|
||||
driver.find_element_by_id('field-description').clear()
|
||||
|
@ -123,22 +150,28 @@ class TestSelenium(unittest.TestCase):
|
|||
driver.find_element_by_name('save').click()
|
||||
|
||||
# Add users
|
||||
driver.find_element_by_link_text('Manage').click()
|
||||
driver.find_element_by_link_text('Members').click()
|
||||
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Manage'))).click()
|
||||
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Members'))).click()
|
||||
for user in users:
|
||||
driver.find_element_by_link_text('Add Member').click()
|
||||
driver.find_element_by_id('username').send_keys(user)
|
||||
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Add Member'))).click()
|
||||
WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, "s2id_autogen1"))).send_keys(user + Keys.RETURN)
|
||||
driver.find_element_by_name('submit').click()
|
||||
|
||||
def fill_ds_general_info(self, name, description, tags, private, searchable, allowed_users, acquire_url):
|
||||
# FIRST PAGE: Dataset properties
|
||||
driver = self.driver
|
||||
WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, "field-title")))
|
||||
|
||||
# Wait a bit to let ckan add javascript hooks
|
||||
time.sleep(0.2)
|
||||
|
||||
driver.find_element_by_id('field-title').clear()
|
||||
driver.find_element_by_id('field-title').send_keys(name)
|
||||
driver.find_element_by_id('field-notes').clear()
|
||||
driver.find_element_by_id('field-notes').send_keys(description)
|
||||
driver.find_element_by_id('field-tags').clear()
|
||||
driver.find_element_by_id('field-tags').send_keys(','.join(tags))
|
||||
# field-tags
|
||||
for tag in tags:
|
||||
driver.find_element_by_id('s2id_autogen1').send_keys(tag + Keys.RETURN)
|
||||
Select(driver.find_element_by_id('field-private')).select_by_visible_text('Private' if private else 'Public')
|
||||
# WARN: The organization is set by default
|
||||
|
||||
|
@ -146,8 +179,9 @@ class TestSelenium(unittest.TestCase):
|
|||
# If the dataset is public, these fields will be disabled (we'll check it)
|
||||
if private:
|
||||
Select(driver.find_element_by_id('field-searchable')).select_by_visible_text('True' if searchable else 'False')
|
||||
driver.find_element_by_id('field-allowed_users_str').clear()
|
||||
driver.find_element_by_id('field-allowed_users_str').send_keys(','.join(allowed_users))
|
||||
# field-allowed_users
|
||||
for user in allowed_users:
|
||||
driver.find_element_by_css_selector('#s2id_field-allowed_users_str .select2-input').send_keys(user + Keys.RETURN)
|
||||
driver.find_element_by_id('field-acquire_url').clear()
|
||||
if acquire_url:
|
||||
driver.find_element_by_id('field-acquire_url').send_keys(acquire_url)
|
||||
|
@ -159,15 +193,20 @@ class TestSelenium(unittest.TestCase):
|
|||
def create_ds(self, name, description, tags, private, searchable, allowed_users, acquire_url, resource_url, resource_name, resource_description, resource_format):
|
||||
driver = self.driver
|
||||
driver.get(self.base_url)
|
||||
driver.find_element_by_link_text('Datasets').click()
|
||||
driver.find_element_by_link_text('Add Dataset').click()
|
||||
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Datasets'))).click()
|
||||
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Add Dataset'))).click()
|
||||
self.fill_ds_general_info(name, description, tags, private, searchable, allowed_users, acquire_url)
|
||||
|
||||
# SECOND PAGE: Add Resources
|
||||
WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, "field-name")))
|
||||
|
||||
# Wait a bit to let ckan add javascript hooks
|
||||
time.sleep(0.2)
|
||||
|
||||
try:
|
||||
# The link button is only clicked if it's present
|
||||
driver.find_element_by_link_text('Link').click()
|
||||
except Exception:
|
||||
except Exception: # pragma: no cover
|
||||
pass
|
||||
|
||||
driver.find_element_by_id('field-image-url').clear()
|
||||
|
@ -176,11 +215,7 @@ class TestSelenium(unittest.TestCase):
|
|||
driver.find_element_by_id('field-name').send_keys(resource_name)
|
||||
driver.find_element_by_id('field-description').clear()
|
||||
driver.find_element_by_id('field-description').send_keys(resource_description)
|
||||
driver.find_element_by_id('s2id_autogen1').clear()
|
||||
driver.find_element_by_id('s2id_autogen1').send_keys(resource_format)
|
||||
driver.find_element_by_css_selector('button.btn.btn-primary').click()
|
||||
|
||||
# THIRD PAGE: Metadata
|
||||
driver.find_element_by_id('s2id_autogen1').send_keys(resource_format + Keys.RETURN)
|
||||
driver.find_element_by_css_selector('button.btn.btn-primary').click()
|
||||
|
||||
def modify_ds(self, url, name, description, tags, private, searchable, allowed_users, acquire_url):
|
||||
|
@ -200,11 +235,12 @@ class TestSelenium(unittest.TestCase):
|
|||
|
||||
# Test that the allowed users lists is as expected (order is not important)
|
||||
current_users = driver.find_element_by_css_selector('#s2id_field-allowed_users_str > ul.select2-choices').text.split('\n')
|
||||
current_users = current_users[0:-1]
|
||||
# ''.split('\n') ==> ['']
|
||||
if len(current_users) == 1 and current_users[0] == '':
|
||||
current_users = []
|
||||
# if len(current_users) == 1 and current_users[0] == '':
|
||||
# current_users = []
|
||||
# Check the array
|
||||
self.assertEquals(len(allowed_users), len(current_users))
|
||||
self.assertEqual(len(allowed_users), len(current_users))
|
||||
for user in current_users:
|
||||
self.assertIn(user, allowed_users)
|
||||
else:
|
||||
|
@ -214,69 +250,56 @@ class TestSelenium(unittest.TestCase):
|
|||
driver = self.driver
|
||||
driver.find_element_by_link_text('Datasets').click()
|
||||
|
||||
if searchable:
|
||||
if searchable or owner or in_org:
|
||||
xpath = '//div[@id=\'content\']/div[3]/div/section/div/ul/li/div/h3/span'
|
||||
|
||||
# Check the label
|
||||
if owner:
|
||||
self.assertEqual('OWNER', driver.find_element_by_xpath(xpath).text)
|
||||
if not acquired and private and not in_org:
|
||||
self.assertEqual('PRIVATE', driver.find_element_by_xpath(xpath).text)
|
||||
elif acquired and not owner and private:
|
||||
self.assertEqual('ACQUIRED', driver.find_element_by_xpath(xpath).text)
|
||||
elif owner:
|
||||
self.assertEqual('OWNER', driver.find_element_by_xpath(xpath).text)
|
||||
|
||||
# Access the dataset
|
||||
driver.find_element_by_link_text(dataset).click()
|
||||
|
||||
# When a user cannot access a dataset, the link is no longer provided
|
||||
else:
|
||||
# If the dataset is not searchable, a link to it could not be found in the dataset search page
|
||||
self.assertEquals(None, re.search(dataset_url, driver.page_source))
|
||||
# If the dataset is not searchable and the user is not the owner, a link to it could not be found in the dataset search page
|
||||
self.assertEqual(None, re.search(dataset_url, driver.page_source))
|
||||
|
||||
# Access the dataset
|
||||
driver.get(self.base_url + 'dataset/' + dataset_url)
|
||||
# Access the dataset
|
||||
driver.get(self.base_url + 'dataset/' + dataset_url)
|
||||
|
||||
if not acquired and private and not in_org:
|
||||
xpath = '//div[@id=\'content\']/div/div'
|
||||
buy_msg = 'This private dataset can be acquired. To do so, please click here'
|
||||
if acquire_url is not None:
|
||||
self.assertTrue(driver.find_element_by_xpath(xpath).text.startswith(buy_msg))
|
||||
self.assertEquals(acquire_url, driver.find_element_by_link_text('here').get_attribute('href'))
|
||||
xpath += '[2]' # The unauthorized message is in a different Path
|
||||
else:
|
||||
src = driver.page_source
|
||||
self.assertEquals(None, re.search(buy_msg, src))
|
||||
# If the dataset is private and the user hasnt access to the resources, the field resources dont appear
|
||||
|
||||
self.assertTrue('/user/login' in driver.current_url)
|
||||
self.assertTrue(driver.find_element_by_xpath(xpath).text.startswith('Unauthorized to read package %s' % dataset_url))
|
||||
self.assertEquals('empty', driver.find_element_by_class_name('empty').get_attribute('class'))
|
||||
self.assertEqual(self.base_url + 'dataset/%s' % dataset_url, driver.current_url)
|
||||
|
||||
else:
|
||||
self.assertEquals(self.base_url + 'dataset/%s' % dataset_url, driver.current_url)
|
||||
self.assertEquals('resource-list', driver.find_element_by_class_name('resource-list').get_attribute('class'))
|
||||
self.assertEqual(self.base_url + 'dataset/%s' % dataset_url, driver.current_url)
|
||||
|
||||
def check_acquired(self, dataset, dataset_url, acquired, private):
|
||||
driver = self.driver
|
||||
driver.get(self.base_url + 'dashboard')
|
||||
driver.find_element_by_link_text('Acquired Datasets').click()
|
||||
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Acquired Datasets'))).click()
|
||||
|
||||
if acquired and private:
|
||||
# This message could not be shown when the user has acquired at least one dataset
|
||||
self.assertEquals(None, re.search('You haven\'t acquired any datasets.', driver.page_source))
|
||||
self.assertEqual(None, re.search('You haven\'t acquired any datasets.', driver.page_source))
|
||||
# Access the dataset
|
||||
driver.find_element_by_link_text(dataset).click()
|
||||
self.assertEquals(self.base_url + 'dataset/%s' % dataset_url, driver.current_url)
|
||||
self.assertEqual(self.base_url + 'dataset/%s' % dataset_url, driver.current_url)
|
||||
else:
|
||||
# If the user has not acquired the dataset, a link to this dataset could not be in the acquired dataset list
|
||||
self.assertEquals(None, re.search(dataset_url, driver.page_source))
|
||||
self.assertEqual(None, re.search(dataset_url, driver.page_source))
|
||||
# When a user has not acquired any dataset, a message will be shown to inform the user
|
||||
self.assertNotEquals(None, re.search('You haven\'t acquired any datasets.', driver.page_source))
|
||||
|
||||
def default_register(self, user):
|
||||
self.register(user, user, '%s@conwet.com' % user, user)
|
||||
self.register(user, user, '%s@conwet.com' % user)
|
||||
|
||||
@parameterized.expand([
|
||||
# (['user1', 'user2', 'user3'], True, True, ['user2'], 'http://store.conwet.com/'),
|
||||
# (['user1', 'user2', 'user3'], True, True, ['user3']),
|
||||
# (['user1', 'user2', 'user3'], False, True, ['user3']),
|
||||
# (['user1', 'user2', 'user3'], True, False, ['user2']),
|
||||
(['user1', 'user2', 'user3'], True, True, [], 'http://store.conwet.com/'),
|
||||
(['user1', 'user2', 'user3'], True, True, []),
|
||||
(['user1', 'user2', 'user3'], False, True, []),
|
||||
|
@ -292,12 +315,14 @@ class TestSelenium(unittest.TestCase):
|
|||
self.default_register(user)
|
||||
|
||||
# The first user creates a dataset
|
||||
self.login(users[0], users[0])
|
||||
self.login(users[0])
|
||||
pkg_name = 'Dataset 1'
|
||||
url = get_dataset_url(pkg_name)
|
||||
self.create_ds(pkg_name, 'Example description', ['tag1', 'tag2', 'tag3'], private, searchable,
|
||||
allowed_users, acquire_url, 'http://upm.es', 'UPM Main', 'Example Description', 'CSV')
|
||||
|
||||
self.check_ds_values(url, private, searchable, allowed_users, acquire_url)
|
||||
|
||||
self.check_user_access(pkg_name, url, True, True, False, private, searchable, acquire_url)
|
||||
self.check_acquired(pkg_name, url, False, private)
|
||||
|
||||
|
@ -305,26 +330,16 @@ class TestSelenium(unittest.TestCase):
|
|||
rest_users = users[1:]
|
||||
for user in rest_users:
|
||||
self.logout()
|
||||
self.login(user, user)
|
||||
self.login(user)
|
||||
acquired = user in allowed_users
|
||||
self.check_user_access(pkg_name, url, False, acquired, False, private, searchable, acquire_url)
|
||||
|
||||
self.check_acquired(pkg_name, url, acquired, private)
|
||||
|
||||
@parameterized.expand([
|
||||
# (['a'] , 'http://upm.es', 'Allowed users: Name must be at least 2 characters long'),
|
||||
# (['a a'], 'http://upm.es', 'Allowed users: Url must be purely lowercase alphanumeric (ascii) characters and these symbols: -_'),
|
||||
(['upm', 'a'], 'http://upm.es', 'Allowed users: Name must be at least 2 characters long'),
|
||||
(['upm', 'a a a'], 'http://upm.es', 'Allowed users: Url must be purely lowercase alphanumeric (ascii) characters and these symbols: -_'),
|
||||
(['upm', 'a?-vz'], 'http://upm.es', 'Allowed users: Url must be purely lowercase alphanumeric (ascii) characters and these symbols: -_'),
|
||||
(['thisisaveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylongname'],
|
||||
'http://upm.es', 'Allowed users: Name must be a maximum of 100 characters long'),
|
||||
(['conwet'], 'ftp://google.es', 'Acquire URL: The URL "ftp://google.es" is not valid.'),
|
||||
# (['conwet'], 'http://google*.com', 'Acquire URL: The URL "http://google*.com" is not valid.'),
|
||||
# (['conwet'], 'http://google+.com', 'Acquire URL: The URL "http://google+.com" is not valid.'),
|
||||
# (['conwet'], 'http://google/.com', 'Acquire URL: The URL "http://google/.com" is not valid.'),
|
||||
(['conwet'], 'google', 'Acquire URL: The URL "google" is not valid.'),
|
||||
(['conwet'], 'http://google', 'Acquire URL: The URL "http://google" is not valid.'),
|
||||
# (['conwet'], 'http://google:es', 'Acquire URL: The URL "http://google:es" is not valid.'),
|
||||
(['conwet'], 'www.google.es', 'Acquire URL: The URL "www.google.es" is not valid.')
|
||||
|
||||
])
|
||||
|
@ -335,21 +350,21 @@ class TestSelenium(unittest.TestCase):
|
|||
self.default_register(user)
|
||||
|
||||
# Create the dataset
|
||||
self.login(user, user)
|
||||
self.login(user)
|
||||
pkg_name = 'Dataset 2'
|
||||
|
||||
# Go the page to create the dataset
|
||||
driver = self.driver
|
||||
driver.get(self.base_url)
|
||||
driver.find_element_by_link_text('Datasets').click()
|
||||
driver.find_element_by_link_text('Add Dataset').click()
|
||||
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Datasets'))).click()
|
||||
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Add Dataset'))).click()
|
||||
|
||||
# Fill the requested information
|
||||
self.fill_ds_general_info(pkg_name, 'Example description', ['tag1'], True, True, allowed_users, acquire_url)
|
||||
|
||||
# Check the error message
|
||||
msg_error = self.driver.find_element_by_xpath('//div[@id=\'content\']/div[3]/div/section/div/form/div/ul/li').text
|
||||
self.assertEquals(expected_msg, msg_error)
|
||||
msg_error = WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.XPATH, '//div[@id=\'content\']/div[3]/div/section/div/form/div/ul/li'))).text
|
||||
self.assertEqual(expected_msg, msg_error)
|
||||
|
||||
@parameterized.expand([
|
||||
('Acquire Dataset', 'dataset'),
|
||||
|
@ -359,13 +374,13 @@ class TestSelenium(unittest.TestCase):
|
|||
# Create a default user
|
||||
user = 'user1'
|
||||
self.default_register(user)
|
||||
self.login(user, user)
|
||||
self.login(user)
|
||||
|
||||
# Enter the acquired dataset tab
|
||||
driver = self.driver
|
||||
driver.get(self.base_url + 'dashboard/acquired')
|
||||
driver.find_element_by_link_text(link).click()
|
||||
self.assertEquals(self.base_url + 'dataset', self.base_url + expected_url)
|
||||
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, link))).click()
|
||||
self.assertEqual(self.base_url + 'dataset', self.base_url + expected_url)
|
||||
|
||||
@parameterized.expand([
|
||||
|
||||
|
@ -390,7 +405,6 @@ class TestSelenium(unittest.TestCase):
|
|||
([{'private': False, 'searchable': True, 'allowed_users': ['user1', 'user2']}], ['user3', 'user4']),
|
||||
([{'private': True, 'searchable': False, 'allowed_users': ['user1', 'user2']}], ['user3', 'user4']),
|
||||
([{'private': False, 'searchable': False, 'allowed_users': ['user1', 'user2']}], ['user3', 'user4']),
|
||||
|
||||
# Complex test
|
||||
([{'private': True, 'searchable': False, 'allowed_users': ['user1', 'user2']},
|
||||
{'private': True, 'searchable': True, 'allowed_users': ['user5', 'user6']},
|
||||
|
@ -402,7 +416,7 @@ class TestSelenium(unittest.TestCase):
|
|||
# Create a default user
|
||||
user = 'user1'
|
||||
self.default_register(user)
|
||||
self.login(user, user)
|
||||
self.login(user)
|
||||
|
||||
acquire_url = 'http://upm.es'
|
||||
dataset_default_name = 'Dataset %d'
|
||||
|
@ -443,29 +457,14 @@ class TestSelenium(unittest.TestCase):
|
|||
self.check_ds_values(url_path, dataset['private'], dataset['searchable'], final_users, acquire_url)
|
||||
|
||||
@parameterized.expand([
|
||||
# (['user1', 'user2', 'user3'], [{'name': 'CoNWeT', 'users': ['user2']}], True, True, [], 'http://store.conwet.com/'),
|
||||
# (['user1', 'user2', 'user3'], [{'name': 'CoNWeT', 'users': ['user2']}], True, True, []),
|
||||
# (['user1', 'user2', 'user3'], [{'name': 'CoNWeT', 'users': ['user2']}], False, True, []),
|
||||
# (['user1', 'user2', 'user3'], [{'name': 'CoNWeT', 'users': ['user2']}], True, False, []),
|
||||
# (['user1', 'user2', 'user3'], [{'name': 'CoNWeT', 'users': ['user2']}], True, True, ['user3'], 'http://store.conwet.com/'),
|
||||
# (['user1', 'user2', 'user3'], [{'name': 'CoNWeT', 'users': ['user2']}], True, True, ['user3']),
|
||||
# (['user1', 'user2', 'user3'], [{'name': 'CoNWeT', 'users': ['user2']}], False, True, ['user3']),
|
||||
# (['user1', 'user2', 'user3'], [{'name': 'CoNWeT', 'users': ['user2']}], True, False, ['user3']),
|
||||
|
||||
# More complex
|
||||
# (['user1', 'user2', 'user3', 'user4', 'user5', 'user6'], [{'name': 'CoNWeT', 'users': ['user2', 'user3']}], True, True, ['user4', 'user5'], 'http://store.conwet.com/'),
|
||||
# (['user1', 'user2', 'user3', 'user4', 'user5', 'user6'], [{'name': 'CoNWeT', 'users': ['user2', 'user3']}], True, True, ['user4', 'user5']),
|
||||
# (['user1', 'user2', 'user3', 'user4', 'user5', 'user6'], [{'name': 'CoNWeT', 'users': ['user2', 'user3']}], False, True, ['user4', 'user5']),
|
||||
# (['user1', 'user2', 'user3', 'user4', 'user5', 'user6'], [{'name': 'CoNWeT', 'users': ['user2', 'user3']}], True, False, ['user4', 'user5']),
|
||||
|
||||
# Even if user6 is in another organization, he/she won't be able to access the dataset
|
||||
(['user1', 'user2', 'user3', 'user4', 'user5', 'user6'], [{'name': 'CoNWeT', 'users': ['user2', 'user3']},
|
||||
(['user1', 'user2', 'user3', 'user4', 'user5', 'user6'], [{'name': 'CoNWeT', 'users': ['user2', 'user3']},
|
||||
{'name': 'UPM', 'users': ['user6']}], True, True, ['user4', 'user5'], 'http://store.conwet.com/'),
|
||||
(['user1', 'user2', 'user3', 'user4', 'user5', 'user6'], [{'name': 'CoNWeT', 'users': ['user2', 'user3']},
|
||||
(['user1', 'user2', 'user3', 'user4', 'user5', 'user6'], [{'name': 'CoNWeT', 'users': ['user2', 'user3']},
|
||||
{'name': 'UPM', 'users': ['user6']}], True, True, ['user4', 'user5']),
|
||||
(['user1', 'user2', 'user3', 'user4', 'user5', 'user6'], [{'name': 'CoNWeT', 'users': ['user2', 'user3']},
|
||||
(['user1', 'user2', 'user3', 'user4', 'user5', 'user6'], [{'name': 'CoNWeT', 'users': ['user2', 'user3']},
|
||||
{'name': 'UPM', 'users': ['user6']}], True, False, ['user4', 'user5']),
|
||||
(['user1', 'user2', 'user3', 'user4', 'user5', 'user6'], [{'name': 'CoNWeT', 'users': ['user2', 'user3']},
|
||||
(['user1', 'user2', 'user3', 'user4', 'user5', 'user6'], [{'name': 'CoNWeT', 'users': ['user2', 'user3']},
|
||||
{'name': 'UPM', 'users': ['user6']}], False, True, ['user4', 'user5']),
|
||||
])
|
||||
def test_organization(self, users, orgs, private, searchable, adquiring_users, acquire_url=None):
|
||||
|
@ -473,7 +472,7 @@ class TestSelenium(unittest.TestCase):
|
|||
for user in users:
|
||||
self.default_register(user)
|
||||
|
||||
self.login(users[0], users[0])
|
||||
self.login(users[0])
|
||||
|
||||
# Create the organizations
|
||||
for org in orgs:
|
||||
|
@ -492,10 +491,11 @@ class TestSelenium(unittest.TestCase):
|
|||
rest_users = users[1:]
|
||||
for user in rest_users:
|
||||
self.logout()
|
||||
self.login(user, user)
|
||||
self.login(user)
|
||||
acquired = user in adquiring_users
|
||||
in_org = user in orgs[0]['users']
|
||||
self.check_user_access(pkg_name, url, False, acquired, in_org, private, searchable, acquire_url)
|
||||
|
||||
self.check_acquired(pkg_name, url, acquired, private)
|
||||
|
||||
def test_bug_16(self):
|
||||
|
@ -506,15 +506,14 @@ class TestSelenium(unittest.TestCase):
|
|||
self.default_register(user)
|
||||
|
||||
# The user creates a dataset
|
||||
self.login(user, user)
|
||||
self.login(user)
|
||||
pkg_name = 'Dataset 1'
|
||||
description = 'Example Description'
|
||||
tags = ['tag1', 'tag2', 'tag3']
|
||||
url = get_dataset_url(pkg_name)
|
||||
self.create_ds(pkg_name, 'Example description', ['tag1', 'tag2', 'tag3'], True, True,
|
||||
self.create_ds(pkg_name, 'Example description', [], True, True,
|
||||
[], 'http://example.com', 'http://upm.es', 'UPM Main', 'Example Description', 'CSV')
|
||||
|
||||
self.modify_ds(url, pkg_name, description, tags, False, None, None, None)
|
||||
expected_url = 'dataset/%s' % url
|
||||
current_url = self.driver.current_url
|
||||
self.assertIn(expected_url, current_url) # Maybe the current URL include some parameters
|
||||
WebDriverWait(self.driver, 20).until(lambda driver: expected_url in driver.current_url)
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import unittest
|
||||
|
||||
from mock import ANY, DEFAULT, MagicMock, patch
|
||||
from parameterized import parameterized
|
||||
|
||||
from ckanext.privatedatasets import views
|
||||
|
||||
|
||||
class ViewsTest(unittest.TestCase):
|
||||
|
||||
@parameterized.expand([
|
||||
('NotFound', 404),
|
||||
('NotAuthorized', 403),
|
||||
])
|
||||
@patch.multiple("ckanext.privatedatasets.views", base=DEFAULT, toolkit=DEFAULT, model=DEFAULT, g=DEFAULT, logic=DEFAULT, _=DEFAULT)
|
||||
def test_exceptions_loading_users(self, exception, expected_status, base, toolkit, model, g, logic, _):
|
||||
|
||||
# Configure the mocks
|
||||
setattr(logic, exception, ValueError)
|
||||
toolkit.get_action().side_effect = getattr(logic, exception)
|
||||
base.abort.side_effect = TypeError
|
||||
|
||||
# Call the function
|
||||
with self.assertRaises(TypeError):
|
||||
views.acquired_datasets()
|
||||
|
||||
# Assertations
|
||||
expected_context = {
|
||||
'auth_user_obj': g.userobj,
|
||||
'for_view': True,
|
||||
'model': model,
|
||||
'session': model.Session,
|
||||
'user': g.user,
|
||||
}
|
||||
|
||||
toolkit.get_action().assert_called_once_with(expected_context, {'user_obj': g.userobj})
|
||||
base.abort.assert_called_once_with(expected_status, ANY)
|
||||
|
||||
@patch.multiple("ckanext.privatedatasets.views", base=DEFAULT, toolkit=DEFAULT, model=DEFAULT, g=DEFAULT, logic=DEFAULT)
|
||||
def test_no_error_loading_users(self, base, toolkit, model, g, logic):
|
||||
|
||||
# actions
|
||||
default_user = {'user_name': 'test', 'another_val': 'example value'}
|
||||
user_show = MagicMock(return_value=default_user)
|
||||
acquisitions_list = MagicMock()
|
||||
|
||||
toolkit.get_action = MagicMock(side_effect=lambda action: user_show if action == 'user_show' else acquisitions_list)
|
||||
|
||||
# Call the function
|
||||
returned = views.acquired_datasets()
|
||||
|
||||
# User_show called correctly
|
||||
expected_context = {
|
||||
'auth_user_obj': g.userobj,
|
||||
'for_view': True,
|
||||
'model': model,
|
||||
'session': model.Session,
|
||||
'user': g.user,
|
||||
}
|
||||
|
||||
user_show.assert_called_once_with(expected_context, {'user_obj': g.userobj})
|
||||
acquisitions_list.assert_called_with(expected_context, None)
|
||||
|
||||
# Check that the render method has been called
|
||||
base.render.assert_called_once_with('user/dashboard_acquired.html', {'user_dict': default_user, 'acquired_datasets': acquisitions_list()})
|
||||
self.assertEqual(returned, base.render())
|
||||
|
||||
@patch("ckanext.privatedatasets.views.acquired_datasets")
|
||||
def test_there_is_a_controller_for_ckan_27(self, acquired_datasets):
|
||||
controller = views.AcquiredDatasetsControllerUI()
|
||||
|
||||
response = controller.acquired_datasets()
|
||||
|
||||
acquired_datasets.assert_called_once_with()
|
||||
self.assertEqual(response, acquired_datasets())
|
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from ckan import logic, model
|
||||
from ckan.common import _, g
|
||||
from ckan.lib import base
|
||||
from ckan.plugins import toolkit
|
||||
|
||||
from ckanext.privatedatasets import constants
|
||||
|
||||
|
||||
def acquired_datasets():
|
||||
context = {'auth_user_obj': g.userobj, 'for_view': True, 'model': model, 'session': model.Session, 'user': g.user}
|
||||
data_dict = {'user_obj': g.userobj}
|
||||
try:
|
||||
user_dict = toolkit.get_action('user_show')(context, data_dict)
|
||||
acquired_datasets = toolkit.get_action(constants.ACQUISITIONS_LIST)(context, None)
|
||||
except logic.NotFound:
|
||||
base.abort(404, _('User not found'))
|
||||
except logic.NotAuthorized:
|
||||
base.abort(403, _('Not authorized to see this page'))
|
||||
|
||||
extra_vars = {
|
||||
'user_dict': user_dict,
|
||||
'acquired_datasets': acquired_datasets,
|
||||
}
|
||||
return base.render('user/dashboard_acquired.html', extra_vars)
|
||||
|
||||
|
||||
class AcquiredDatasetsControllerUI(base.BaseController):
|
||||
|
||||
def acquired_datasets(self):
|
||||
return acquired_datasets()
|
|
@ -1,2 +0,0 @@
|
|||
nose_parameterized==0.3.3
|
||||
selenium==2.44.0
|
|
@ -0,0 +1,21 @@
|
|||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[flake8]
|
||||
ignore=E501
|
||||
|
||||
[metadata]
|
||||
description-file = README.md
|
||||
|
||||
[nosetests]
|
||||
ckan=1
|
||||
with-pylons=test.ini
|
||||
with-xunit=1
|
||||
with-coverage=1
|
||||
cover-package=ckanext.privatedatasets
|
||||
cover-inclusive=1
|
||||
cover-erase=1
|
||||
cover-xml=1
|
||||
|
||||
[pep8]
|
||||
ignore=E501
|
23
setup.py
23
setup.py
|
@ -19,29 +19,38 @@
|
|||
# along with CKAN Private Dataset Extension. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
import sys, os
|
||||
|
||||
version = '0.2.7'
|
||||
version = '0.4.1'
|
||||
|
||||
setup(
|
||||
name='ckanext-privatedatasets',
|
||||
version=version,
|
||||
description="This extensions allows users to create private datasets only visible to certain users. The extension provides also an API to specify programatically which users can access private datasets",
|
||||
description='CKAN Extension - Private Datasets',
|
||||
long_description='''
|
||||
This CKAN extension allows a user to create private datasets that only certain users will be able to see. When a dataset is being created, it's possible to specify the list of users that can see this dataset. In addition, the extension provides an HTTP API that allows to add users programatically.
|
||||
''',
|
||||
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
keywords='',
|
||||
author='Aitor Magan',
|
||||
author_email='amagan@conwet.com',
|
||||
url='',
|
||||
keywords='ckan, private, datasets',
|
||||
author='Aitor Magan, Francisco de la Vega',
|
||||
author_email='fdelavega@ficodes.com',
|
||||
url='https://conwet.fi.upm.es',
|
||||
download_url='https://github.com/conwetlab/ckanext-privatedatasets/tarball/v' + version,
|
||||
license='',
|
||||
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
|
||||
namespace_packages=['ckanext', 'ckanext.privatedatasets'],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
setup_requires=[
|
||||
'nose>=1.3.0'
|
||||
],
|
||||
install_requires=[
|
||||
# -*- Extra requirements: -*-
|
||||
],
|
||||
tests_require=[
|
||||
'parameterized',
|
||||
'selenium==3.13.0'
|
||||
],
|
||||
test_suite='nosetests',
|
||||
entry_points='''
|
||||
[ckan.plugins]
|
||||
# Add plugins here, e.g.
|
||||
|
|
70
test.ini
70
test.ini
|
@ -4,10 +4,76 @@ host = 0.0.0.0
|
|||
port = 5000
|
||||
|
||||
[app:main]
|
||||
# use = config:/usr/lib/ckan/default/src/ckan/test-core.ini
|
||||
use = config:./ckan/test-core.ini
|
||||
|
||||
ckan.legacy_templates = No
|
||||
ckan.site_id = ckanext.privatedatasets.test
|
||||
ckan.site_url = http://localhost:5000
|
||||
package_new_return_url = http://localhost:5000/dataset/<NAME>?test=new
|
||||
package_edit_return_url = http://localhost:5000/dataset/<NAME>?test=edit
|
||||
|
||||
ckan.cache_validation_enabled = True
|
||||
ckan.cache_enabled = False
|
||||
ckan.tests.functional.test_cache.expires = 1800
|
||||
ckan.tests.functional.test_cache.TestCacheBasics.test_get_cache_expires.expires = 3600
|
||||
|
||||
# Disable test css as we aren't using it and creates requests to URLs
|
||||
# raising ValidationErrors. See #46
|
||||
ckan.template_head_end = <!-- template_head_end -->
|
||||
ckan.legacy_templates = no
|
||||
ckan.plugins = privatedatasets
|
||||
|
||||
ckan.auth.create_unowned_dataset = true
|
||||
ckan.auth.create_dataset_if_not_in_organization = true
|
||||
ckan.auth.user_create_groups = true
|
||||
ckan.auth.user_create_organizations = true
|
||||
|
||||
ckan.privatedatasets.parser = ckanext.privatedatasets.parsers.fiware:FiWareNotificationParser
|
||||
ckan.privatedatasets.show_acquire_url_on_create = True
|
||||
ckan.privatedatasets.show_acquire_url_on_edit = True
|
||||
ckan.privatedatasets.show_acquire_url_on_edit = True
|
||||
|
||||
sqlalchemy.url = postgresql://ckan_default:pass@127.0.0.1:5432/ckan_test
|
||||
ckan.datastore.write_url = postgresql://ckan_default:pass@127.0.0.1:5432/datastore_test
|
||||
ckan.datastore.read_url = postgresql://datastore_default:pass@127.0.0.1:5432/datastore_test
|
||||
|
||||
ckan.storage_path=data/storage
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root, ckan, ckanext, sqlalchemy
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
|
||||
[logger_ckan]
|
||||
qualname = ckan
|
||||
handlers = console
|
||||
level = INFO
|
||||
propagate = 0
|
||||
|
||||
[logger_ckanext]
|
||||
qualname = ckanext
|
||||
handlers = console
|
||||
level = DEBUG
|
||||
propagate = 0
|
||||
|
||||
[logger_sqlalchemy]
|
||||
handlers = console
|
||||
qualname = sqlalchemy.engine
|
||||
level = WARN
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stdout,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s
|
||||
|
|
Loading…
Reference in New Issue