Commit f479d264 authored by Pablo Panero's avatar Pablo Panero
Browse files

Merge branch 'dev' into 'master'

Refactor permission factory

Closes #4

See merge request webservices/cern-search/cern-search-rest-api!46
parents acedc9e0 660083a7
{
"title": "Custom record schema v0.0.1",
"id": "http://localhost:5000/schemas/cernsearch-test/test-doc_v0.0.1.json",
"$schema": "http://localhost:5000/schemas/cernsearch-test/test-doc_v0.0.1.json",
"type": "object",
"properties": {
"_access": {
"type": "object",
"properties": {
"owner":{
"type": "array",
"items": {
"type": "string"
}
},
"read":{
"type": "array",
"items": {
"type": "string"
}
},
"update":{
"type": "array",
"items": {
"type": "string"
}
},
"delete":{
"type": "array",
"items": {
"type": "string"
}
}
}
},
"content": {
"type": "string"
},
"custom_pid": {
"type": "string"
},
"$schema": {
"type": "string"
}
}
}
\ No newline at end of file
......@@ -14,7 +14,7 @@
},
"properties": {
"_access": {
"type": "nested",
"type": "object",
"properties": {
"owner":{
"type": "keyword"
......
......@@ -14,7 +14,7 @@
},
"properties": {
"_access": {
"type": "nested",
"type": "object",
"properties": {
"owner":{
"type": "keyword"
......
......@@ -14,7 +14,7 @@
},
"properties": {
"_access": {
"type": "nested",
"type": "object",
"properties": {
"owner":{
"type": "keyword"
......
{
"settings": {
"index.percolator.map_unmapped_fields_as_string": true,
"index.mapping.total_fields.limit": 3000
},
"mappings": {
"permission_v0.0.1": {
"numeric_detection": true,
"_meta": {
"_owner": "CernSearch-Administrators@cern.ch"
},
"_all": {
"analyzer": "english"
},
"properties": {
"_access": {
"type": "object",
"properties": {
"owner":{
"type": "keyword"
},
"read": {
"type": "keyword"
},
"update": {
"type": "keyword"
},
"delete": {
"type": "keyword"
}
}
},
"type": {
"type": "text"
},
"custom_pid": {
"type": "string",
"index": "not_analyzed"
},
"$schema": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
}
\ No newline at end of file
......@@ -11,7 +11,7 @@
},
"properties": {
"_access": {
"type": "nested",
"type": "object",
"properties": {
"owner":{
"type": "keyword"
......
......@@ -11,7 +11,7 @@
},
"properties": {
"_access": {
"type": "nested",
"type": "object",
"properties": {
"owner":{
"type": "keyword"
......
......@@ -11,7 +11,7 @@
},
"properties": {
"_access": {
"type": "nested",
"type": "object",
"properties": {
"owner":{
"type": "keyword"
......
......@@ -11,7 +11,7 @@
},
"properties": {
"_access": {
"type": "nested",
"type": "object",
"properties": {
"owner":{
"type": "keyword"
......
......@@ -11,7 +11,7 @@
},
"properties": {
"_access": {
"type": "nested",
"type": "object",
"properties": {
"owner":{
"type": "keyword"
......
......@@ -11,7 +11,7 @@
},
"properties": {
"_access": {
"type": "nested",
"type": "object",
"properties": {
"owner":{
"type": "keyword"
......
......@@ -126,12 +126,22 @@ def has_update_permission(user, record):
# Allow based in the '_access' key
user_provides = get_user_provides()
# set.isdisjoint() is faster than set.intersection()
update_access_groups = record['_access']['update']
if check_elasticsearch(record) and user_provides and has_owner_permission(user) and \
(
not set(user_provides).isdisjoint(set(update_access_groups))
or is_admin(user)
):
update_access_groups = get_access_set(record['_access'], 'update')
delete_access_groups = get_access_set(record['_access'], 'delete')
owner_access_groups = get_access_set(record['_access'], 'owner')
# If the record exists in ES
# The user provides egroup/membership
# The user is owner of the collection/schema
# The user is admin of the instance or any access group list is disjoint
# Then grant access
if is_admin(user) or (
check_elasticsearch(record) and user_provides and has_owner_permission(user) and \
(
not set(user_provides).isdisjoint(set(update_access_groups))
or not set(user_provides).isdisjoint(set(delete_access_groups))
or not set(user_provides).isdisjoint(set(owner_access_groups))
)
):
current_app.logger.debug('Group sets not disjoint, user allowed')
return True
return False
......@@ -144,16 +154,28 @@ def has_read_record_permission(user, record):
# Allow based in the '_access' key
user_provides = get_user_provides()
# set.isdisjoint() is faster than set.intersection()
try:
read_access_groups = record['_access']['read']
if check_elasticsearch(record) and user_provides and has_owner_permission(user) and \
(
not set(user_provides).isdisjoint(set(read_access_groups))
or is_admin(user)
):
current_app.logger.debug('Group sets not disjoint, user allowed')
return True
except KeyError:
read_access_groups = get_access_set(record['_access'], 'read')
update_access_groups = get_access_set(record['_access'], 'update')
delete_access_groups = get_access_set(record['_access'], 'delete')
owner_access_groups = get_access_set(record['_access'], 'owner')
if not read_access_groups:
return True
# If the record exists in ES
# The user provides egroup/membership
# The user is owner of the collection/schema
# The user is admin of the instance or any access group list is disjoint
# Then grant access
if is_admin(user) or (
check_elasticsearch(record) and user_provides and has_owner_permission(user) and \
(
not set(user_provides).isdisjoint(set(read_access_groups))
or not set(user_provides).isdisjoint(set(update_access_groups))
or not set(user_provides).isdisjoint(set(delete_access_groups))
or not set(user_provides).isdisjoint(set(owner_access_groups))
)
):
current_app.logger.debug('Group sets not disjoint, user allowed')
return True
return False
......@@ -165,12 +187,20 @@ def has_delete_permission(user, record):
# Allow based in the '_access' key
user_provides = get_user_provides()
# set.isdisjoint() is faster than set.intersection()
delete_access_groups = record['_access']['delete']
if check_elasticsearch(record) and user_provides and has_owner_permission(user) and \
(
not set(user_provides).isdisjoint(set(delete_access_groups))
or is_admin(user)
):
delete_access_groups = get_access_set(record['_access'], 'delete')
owner_access_groups = get_access_set(record['_access'], 'owner')
# If the record exists in ES
# The user provides egroup/membership
# The user is owner of the collection/schema
# The user is admin of the instance or any access group list is disjoint
# Then grant access
if is_admin(user) or (
check_elasticsearch(record) and user_provides and has_owner_permission(user) and \
(
not set(user_provides).isdisjoint(set(delete_access_groups))
or not set(user_provides).isdisjoint(set(owner_access_groups))
)
):
current_app.logger.debug('Group sets not disjoint, user allowed')
return True
return False
......@@ -218,7 +248,6 @@ def has_admin_view_permission(user):
# Utility functions
def deny(user, record):
"""Deny access."""
return False
......@@ -229,6 +258,13 @@ def allow(user, record):
return True
def get_access_set(access, set):
try:
return access[set]
except KeyError:
return []
def is_admin(user):
"""Check if the user is administrator"""
admin_user = current_app.config['ADMIN_USER']
......
......@@ -16,22 +16,13 @@ curl -X GET "localhost:9200/_search" -H 'Content-Type: application/json' -d'
"filter": {
"bool": {
"should": [
{"nested": {
"path": "_access",
"query": {
"bool": {
"should": [
{"terms": {"_access.read": ["egroup-read-one","egroup-read-two"]}},
{"terms": {"_access.update": "egroup-write-one"}},
{"bool": { # Public document
"must_not": {
"exists": {"field": "_access.read"}
} # End must_not
}} # End bool
] # End should
} # End bool
} # End query
}} # End nested
{"terms": {"_access.read": ["egroup-read-one","egroup-read-two"]}},
{"terms": {"_access.update": "egroup-write-one"}},
{"bool": { # Public document
"must_not": {
"exists": {"field": "_access.read"}
} # End must_not
}} # End bool
] # End should
} # End bool
} # End filter
......@@ -47,18 +38,19 @@ def cern_search_filter():
provides = get_egroups()
# Filter for public records
public = ~Q('exists', field='_access.read')
nested_query = public
cern_filter = public
if provides is not None:
# Filter for restricted records, that the user has access to
read_restricted = Q('terms', **{'_access.read': provides})
write_restricted = Q('terms', **{'_access.update': provides})
delete_restricted = Q('terms', **{'_access.delete': provides})
# Filter records where the user is owner
owner = Q('terms', **{'_access.owner': provides})
# OR all the filters
nested_query = public | read_restricted | write_restricted | owner
cern_filter = public | read_restricted | write_restricted | delete_restricted | owner
return Q('bool', should=[Q('nested', path='_access', query=nested_query)])
return Q('bool', filter=cern_filter)
def get_egroups():
......
......@@ -11,7 +11,7 @@
},
"properties": {
"_access": {
"type": "nested",
"type": "object",
"properties": {
"owner":{
"type": "keyword"
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment