Skip to content

Commit

Permalink
Merge 250a3f1 into 33bce7e
Browse files Browse the repository at this point in the history
  • Loading branch information
Sébastien Délèze committed Aug 30, 2021
2 parents 33bce7e + 250a3f1 commit a63a02a
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 11 deletions.
4 changes: 4 additions & 0 deletions sonar/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,8 @@ def _(x):
RECORDS_REST_FACETS = {
'documents':
dict(aggs=dict(
masked=dict(terms=dict(field='masked',
size=DEFAULT_AGGREGATION_SIZE)),
subdivision=dict(terms=dict(field='subdivisions.pid',
size=DEFAULT_AGGREGATION_SIZE)),
organisation=dict(terms=dict(field='organisation.pid',
Expand Down Expand Up @@ -549,6 +551,8 @@ def _(x):
customField3=dict(terms=dict(field='customField3.raw',
size=DEFAULT_AGGREGATION_SIZE))),
filters={
'masked':
and_term_filter('masked'),
'subdivision':
and_term_filter('subdivisions.pid'),
'organisation':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1835,10 +1835,29 @@
},
"masked": {
"title": "Masked",
"type": "boolean",
"type": "string",
"enum": [
"not_masked",
"masked_for_all",
"masked_for_external_ips"
],
"description": "A masked document is visible in the professional interface, but not in the public interface.",
"default": false,
"default": "not_masked",
"form": {
"options": [
{
"label": "Not masked",
"value": "not_masked"
},
{
"label": "Masked for all",
"value": "masked_for_all"
},
{
"label": "Masked for external IP addresses",
"value": "masked_for_external_ips"
}
],
"expressionProperties": {
"templateOptions.required": "true"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@
},
"name": {
"type": "text"
},
"ips": {
"type": "keyword"
}
}
},
Expand Down Expand Up @@ -584,7 +587,7 @@
}
},
"masked": {
"type": "boolean"
"type": "keyword"
},
"subdivisions": {
"type": "object",
Expand Down
2 changes: 1 addition & 1 deletion sonar/modules/documents/marshmallow/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class DocumentMetadataSchemaV1(StrictKeysMixin):
customField1 = fields.List(fields.String(validate=validate.Length(min=1)))
customField2 = fields.List(fields.String(validate=validate.Length(min=1)))
customField3 = fields.List(fields.String(validate=validate.Length(min=1)))
masked = fields.Boolean()
masked = SanitizedUnicode()
_bucket = SanitizedUnicode()
_files = Nested(FileSchemaV1, many=True)
_oai = fields.Dict()
Expand Down
34 changes: 33 additions & 1 deletion sonar/modules/documents/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from sonar.modules.query import default_search_factory, \
get_operator_and_query_type
from sonar.modules.users.api import current_user_record
from sonar.modules.utils import get_current_ip

FIELDS = [
'_bucket', '_files.*', 'pid', 'organisation.*', 'title.*^3',
Expand Down Expand Up @@ -82,7 +83,38 @@ def search_factory(self, search, query_parser=None):
# Public search
if view:
# Don't display masked records
search = search.filter('bool', must_not={'term': {'masked': True}})
search = search.filter('bool',
should=[{
'bool': {
'must_not': [{
'exists': {
'field': 'masked'
}
}]
}
}, {
'bool': {
'filter': [{
'term': {
'masked': 'not_masked'
}
}]
}
}, {
'bool': {
'must': [{
'term': {
'masked':
'masked_for_external_ips'
}
}, {
'term': {
'organisation.ips':
get_current_ip()
}
}]
}
}])

# Filter record by organisation view.
if view != current_app.config.get('SONAR_APP_DEFAULT_ORGANISATION'):
Expand Down
10 changes: 9 additions & 1 deletion sonar/modules/documents/receivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from sonar.modules.api import SonarRecord
from sonar.modules.documents.api import DocumentRecord
from sonar.modules.documents.loaders.schemas.factory import LoaderSchemaFactory
from sonar.modules.utils import chunks
from sonar.modules.utils import chunks, get_ips_list
from sonar.webdav import HegClient

from .api import DocumentRecord
Expand Down Expand Up @@ -109,6 +109,14 @@ def enrich_document_data(sender=None,
# Check if record is open access.
json['isOpenAccess'] = record.is_open_access()

# Compile allowed IPs in document
if json.get('organisation'):
if json['organisation'][0].get('allowedIps'):
json['organisation'][0]['ips'] = get_ips_list(
json['organisation'][0]['allowedIps'].split('\n'))
else:
json['organisation'][0]['ips'] = ['prout']

# No files are present in record
if not record.files:
return
Expand Down
40 changes: 39 additions & 1 deletion sonar/modules/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import datetime
import re

from flask import current_app, g
from flask import current_app, g, request
from invenio_i18n.ext import current_i18n
from invenio_mail.api import TemplatedMessage
from netaddr import IPAddress, IPGlob, IPNetwork, IPSet
Expand Down Expand Up @@ -278,3 +278,41 @@ def get_bibliographic_code_from_language(language_code):
return key

raise Exception(f'Language code not found for "{language_code}"')


def get_current_ip():
"""Get current IP address.
:returns: Current IP address.
:rtype: str
"""
ip_address = request.environ.get('X-Forwarded-For', request.remote_addr)
# Take only the first IP, as X-Forwarded for gives the real IP + the
# proxy IP.
return ip_address.split(', ')[0]


def get_ips_list(ranges):
"""Get the IP addresses list from a list of ranges.
:param list ranges: List of ranges.
:returns: List of IP addresses.
:rtype: list
"""
ip_set = IPSet()

for ip_range in ranges:
try:
# It's a glob
if '*' in ip_range or '-' in ip_range:
ip_set.add(IPGlob(ip_range))
# It's a network
elif '/' in ip_range:
ip_set.add(IPNetwork(ip_range))
# Simple IP
else:
ip_set.add(IPAddress(ip_range))
except Exception:
pass

return [str(ip) for ip in ip_set]
27 changes: 23 additions & 4 deletions tests/api/documents/test_documents_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,46 @@ def test_collection_query(db, client, document, collection, es_clear):
assert not res.json['aggregations'].get('collection')


def test_masked_document(db, client, document, es_clear):
def test_masked_document(db, client, organisation, document, es_clear):
"""Test masked document."""
# Not masked (property not exists)
res = client.get(url_for('invenio_records_rest.doc_list', view='global'))
assert res.status_code == 200
assert res.json['hits']['total']['value'] == 1

# Not masked
document['masked'] = False
document['masked'] = 'not_masked'
document.commit()
document.reindex()
db.session.commit()
res = client.get(url_for('invenio_records_rest.doc_list', view='global'))
assert res.status_code == 200
assert res.json['hits']['total']['value'] == 1

# Masked
document['masked'] = True
# Masked for all
document['masked'] = 'masked_for_all'
document.commit()
document.reindex()
db.session.commit()
res = client.get(url_for('invenio_records_rest.doc_list', view='global'))
assert res.status_code == 200
assert res.json['hits']['total']['value'] == 0

# Masked for external IPs, IP is not allowed
document['masked'] = 'masked_for_external_ips'
document.commit()
document.reindex()
db.session.commit()
res = client.get(url_for('invenio_records_rest.doc_list', view='global'))
assert res.status_code == 200
assert res.json['hits']['total']['value'] == 0

# Masked for external IPs, IP is allowed
organisation['allowedIps'] = '127.0.0.1'
organisation.commit()
db.session.commit()
organisation.reindex()
document.reindex()
res = client.get(url_for('invenio_records_rest.doc_list', view='global'))
assert res.status_code == 200
assert res.json['hits']['total']['value'] == 1
16 changes: 16 additions & 0 deletions tests/ui/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def test_get_view_code(app, organisation):
with app.test_request_context('/notexists'):
assert get_view_code() == 'global'


def test_format_date():
"""Test date formatting."""
# Just year
Expand Down Expand Up @@ -219,3 +220,18 @@ def test_get_language_value(app):

# Non existing locale
assert get_language_value(values, 'de') == 'Value ENG'


def test_get_current_ip(app):
"""Test get current ip."""
with app.test_request_context(
environ_base={'X-Forwarded-For': '127.0.0.1'}):
assert get_current_ip() == '127.0.0.1'


def test_get_ips_list():
"""Test get IP list."""
ranges = ['127.0.0.1', '192.168.1.3-5', '12.13.14.15/32']
assert get_ips_list(ranges) == [
'12.13.14.15', '127.0.0.1', '192.168.1.3', '192.168.1.4', '192.168.1.5'
]

0 comments on commit a63a02a

Please sign in to comment.