permissions.py 6.52 KB
Newer Older
1
#!/usr/bin/python
2
3
4
# -*- coding: utf-8 -*-

from flask_security import current_user
Pablo Panero's avatar
Pablo Panero committed
5
from flask import request, g, current_app
Pablo Panero's avatar
Oauth    
Pablo Panero committed
6
from invenio_search import current_search_client
7

Pablo Panero's avatar
Pablo Panero committed
8
9
from cern_search_rest.modules.cernsearch.utils import get_user_provides

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
"""Access control for CERN Search."""


def record_permission_factory(record=None, action=None):
    """Record permission factory."""
    return RecordPermission.create(record, action)


def record_create_permission_factory(record=None):
    """Create permission factory."""
    return record_permission_factory(record=record, action='create')


def record_read_permission_factory(record=None):
    """Read permission factory."""
    return record_permission_factory(record=record, action='read')


def record_update_permission_factory(record=None):
    """Update permission factory."""
    return record_permission_factory(record=record, action='update')


def record_delete_permission_factory(record=None):
    """Delete permission factory."""
    return record_permission_factory(record=record, action='delete')


class RecordPermission(object):
    """Record permission.
40
41
    - Create action given to owners only.
    - Read access given to everyone if public, according to a record ownership if not.
42
    - Update access given to record owners.
43
    - Delete access given to record owners.
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
    """

    create_actions = ['create']
    read_actions = ['read']
    update_actions = ['update']
    delete_actions = ['delete']

    def __init__(self, record, func, user):
        """Initialize a file permission object."""
        self.record = record
        self.func = func
        self.user = user or current_user

    def can(self):
        """Determine access."""
        return self.func(self.user, self.record)

    @classmethod
    def create(cls, record, action, user=None):
        """Create a record permission."""
        # Allow everything for testing
        if action in cls.create_actions:
66
            return cls(record, has_owner_permission, user)
67
        elif action in cls.read_actions:
Pablo Panero's avatar
Oauth    
Pablo Panero committed
68
            return cls(record, has_read_record_permission, user)
69
        elif action in cls.update_actions:
Pablo Panero's avatar
Oauth    
Pablo Panero committed
70
            return cls(record, has_update_permission, user)
71
        elif action in cls.delete_actions:
Pablo Panero's avatar
Oauth    
Pablo Panero committed
72
            return cls(record, has_delete_permission, user)
73
74
75
76
        else:
            return cls(record, deny, user)


77
def has_owner_permission(user, record=None):
78
79
80
81
    """Check if user is authenticated and has create access"""
    if user.is_authenticated:
        # Allow based in the '_access' key
        user_provides = get_user_provides()
Pablo Panero's avatar
Oauth    
Pablo Panero committed
82
83
84
85
86
87
88
89
90
        user_index = request.args.get("index")
        index_exists, es_index = parse_index(user_index)
        if index_exists and current_search_client.indices.exists([es_index]):
            mapping = current_search_client.indices.get_mapping([es_index])
            if mapping is not None:
                # set.isdisjoint() is faster than set.intersection()
                create_access_groups = mapping[es_index]['mappings'][user_index]['_meta']['_owner'].split(',')
                if user_provides and not set(user_provides).isdisjoint(set(create_access_groups)):
                    return True
91
    return False
92
93


Pablo Panero's avatar
Oauth    
Pablo Panero committed
94
95
96
97
98
99
100
101
102
103
INDEX_PREFIX = 'cernsearch'


def parse_index(index):
    if index is not None:
        return True, '{0}-{1}'.format(INDEX_PREFIX, index)
    else:
        return False, None


104
def has_update_permission(user, record):
105
106
107
108
109
    """Check if user is authenticated and has update access"""
    if user.is_authenticated:
        # Allow based in the '_access' key
        user_provides = get_user_provides()
        # set.isdisjoint() is faster than set.intersection()
Pablo Panero's avatar
Oauth    
Pablo Panero committed
110
        update_access_groups = record['_access']['update'].split(',')
111
112
        if (user_provides and not set(user_provides).isdisjoint(set(update_access_groups))) \
                or has_owner_permission(user):
113
114
            return True
    return False
115
116


117
118
119
120
121
122
def has_read_record_permission(user, record):
    """Check if user is authenticated and has read access. This implies reading one document"""
    if user.is_authenticated:
        # Allow based in the '_access' key
        user_provides = get_user_provides()
        # set.isdisjoint() is faster than set.intersection()
Pablo Panero's avatar
Oauth    
Pablo Panero committed
123
        read_access_groups = record['_access']['read'].split(',')
124
125
        if (user_provides and not set(user_provides).isdisjoint(set(read_access_groups))) \
                or has_owner_permission(user):
126
127
            return True
    return False
128
129


130
131
132
133
134
135
def has_delete_permission(user, record):
    """Check if user is authenticated and has delete access"""
    if user.is_authenticated:
        # Allow based in the '_access' key
        user_provides = get_user_provides()
        # set.isdisjoint() is faster than set.intersection()
Pablo Panero's avatar
Oauth    
Pablo Panero committed
136
        delete_access_groups = record['_access']['delete'].split(',')
137
138
        if (user_provides and not set(user_provides).isdisjoint(set(delete_access_groups))) \
                or has_owner_permission(user):
139
140
            return True
    return False
141
142


Pablo Panero's avatar
Pablo Panero committed
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
"""Access control for CERN Search Admin Web UI."""


def admin_permission_factory(view):
    """Record permission factory."""
    return AdminPermission.create(view=view)


class AdminPermission(object):

    def __init__(self, func, user, view):
        """Initialize a file permission object."""
        self.user = user or current_user
        self.func = func
        self.view = view

    def can(self):
        """Determine access."""
        return self.func(self.user)

    @classmethod
    def create(cls, user=None, view=None):
        """Create a record permission."""
        # Allow everything for testing
        return cls(has_admin_view_permission, user, view)


def has_admin_view_permission(user):
    admin_access_groups = current_app.config['ADMIN_VIEW_ACCESS_GROUPS']
    if user.is_authenticated and admin_access_groups:
        # Allow based in the '_access' key
        user_provides = get_user_provides()
        # set.isdisjoint() is faster than set.intersection()
        admin_access_groups = admin_access_groups.split(',')
        if user_provides and not set(user_provides).isdisjoint(set(admin_access_groups)):
            return True
    return False


182
# Utility functions
183
184


185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def deny(user, record):
    """Deny access."""
    return False


def allow(user, record):
    """Allow access."""
    return True


def is_public(data, action):
    """Check if the record is fully public.
    In practice this means that the record doesn't have the ``access`` key or
    the action is not inside access or is empty.
    """
    return '_access' not in data or not data.get('_access', {}).get(action)