clients.py 7.55 KB
Newer Older
Lukas Burgey's avatar
Lukas Burgey committed
1
# pylint: disable=too-many-return-statements
2
3

import logging
Lukas Burgey's avatar
Lukas Burgey committed
4
5

from django.contrib.auth.models import Group
6
from django.http import HttpResponse
Lukas Burgey's avatar
Lukas Burgey committed
7
from django.conf import settings
8
from django.contrib.auth import authenticate
9
from django.contrib.sessions.models import Session
Lukas Burgey's avatar
Lukas Burgey committed
10

Lukas Burgey's avatar
Lukas Burgey committed
11
from .... import models
12
13
14

LOGGER = logging.getLogger(__name__)

15
16
17
ALLOW = HttpResponse('allow')
DENY = HttpResponse('deny')

18

19
def _valid_vhost(request):
Lukas Burgey's avatar
Lukas Burgey committed
20
    if request.POST.get('vhost') == models.RabbitMQInstance.load().vhost:
21
22
23
24
        return True
    LOGGER.error('illegal vhost requested')
    return False

Lukas Burgey's avatar
Lukas Burgey committed
25

26
def _valid_permission(request):
27
    perm = request.POST.get('permission')
Lukas Burgey's avatar
Lukas Burgey committed
28
    if perm != 'write':
29
        return True
30
    LOGGER.error('illegal permission requested %s', perm)
31
32
    return False

Lukas Burgey's avatar
Lukas Burgey committed
33

34
def _valid_user(request):
35
36
    return _apiclient_valid(request) or _webpage_client_userid(request)

Lukas Burgey's avatar
Lukas Burgey committed
37

38
def _apiclient_valid(request):
Lukas Burgey's avatar
Lukas Burgey committed
39
    valid = models.User.objects.filter(
40
41
42
43
44
        user_type='apiclient',
        username=request.POST.get('username'),
    ).exists()
    if valid:
        return True
45
46
    return False

Lukas Burgey's avatar
Lukas Burgey committed
47

48
def _apiclient_get(request):
Lukas Burgey's avatar
Lukas Burgey committed
49
    user = models.User.objects.filter(
50
51
52
53
54
55
        user_type='apiclient',
    ).get(
        username=request.POST.get('username'),
    )
    if user:
        return user
56
    LOGGER.error('unable to get user for request')
57
58
    return None

Lukas Burgey's avatar
Lukas Burgey committed
59

60
def _webpage_client_userid(request):
Lukas Burgey's avatar
Lukas Burgey committed
61
    userid = ''
62
63
64
65
    username = request.POST.get('username')
    if username.startswith('webpage-client:'):
        components = username.split(':', maxsplit=1)
        if len(components) == 2:
Lukas Burgey's avatar
Lukas Burgey committed
66
67
            userid = components[1]
    return userid
68

Lukas Burgey's avatar
Lukas Burgey committed
69

70
71
def _webpage_client_valid(request):
    userid = _webpage_client_userid(request)
Lukas Burgey's avatar
Lukas Burgey committed
72
73
74
75
76
77
78
79
80
    try:
        session = Session.objects.get(
            session_key=request.POST.get('password'),
        )
        return session.get_decoded().get('_auth_user_id') == userid

    except Session.DoesNotExist:
        LOGGER.info("User %s has no session", userid)
        return False
81

82
83
# VIEWS: authentication and authorization for
# apiclients and webpage-clients
84

Lukas Burgey's avatar
Lukas Burgey committed
85

86
87
def user_endpoint(request):
    if _webpage_client_valid(request):
Lukas Burgey's avatar
Lukas Burgey committed
88
        # LOGGER.info('Authenticated webpage client')
89
        return ALLOW
90

91
92
93
94
    user = authenticate(
        username=request.POST.get('username'),
        password=request.POST.get('password'),
    )
Lukas Burgey's avatar
Lukas Burgey committed
95
    if user is not None:
Lukas Burgey's avatar
Lukas Burgey committed
96
        # LOGGER.info('Authenticated client as %s', user)
97
        return ALLOW
98

99
100
    LOGGER.error('Failed to authenticate user for RabbitMQ')
    return DENY
101

Lukas Burgey's avatar
Lukas Burgey committed
102

103
def vhost_endpoint(request):
104
    # check if on the correct virtual host
105
106
    if _valid_vhost(request) and _valid_user(request):
        return ALLOW
107

108
109
110
    LOGGER.error('Authorization check for vhost failed for %s', request.POST)
    return DENY

Lukas Burgey's avatar
Lukas Burgey committed
111

112
113
114
115
116
117
def _resource_authorized_webpage_client(request):
    resource = request.POST.get('resource')
    name = request.POST.get('name', '')
    permission = request.POST.get('permission', [])
    return (
        resource == 'exchange'
Lukas Burgey's avatar
Lukas Burgey committed
118
        and name == 'users'
Lukas Burgey's avatar
Lukas Burgey committed
119
        and 'write' not in permission
120
121
122
123
124
125
126
127
    ) or (
        resource == 'queue'
        and name.startswith('stomp-subscription-')
    ) or (
        resource == 'topic'
        and name == _webpage_client_userid(request)
    )

Lukas Burgey's avatar
Lukas Burgey committed
128

129
130
131
132
133
134
135
136
137
def _resource_authorized_apiclient(request):
    resource = request.POST.get('resource')
    name = request.POST.get('name', '')
    permission = request.POST.get('permission', [])
    return (
        resource == 'queue'
        and name.startswith('amq.gen-')
    ) or (
        resource == 'exchange'
Lukas Burgey's avatar
Lukas Burgey committed
138
        and name in models.RabbitMQInstance.load().exchanges
Lukas Burgey's avatar
Lukas Burgey committed
139
        and 'write' not in permission
140
141
    )

Lukas Burgey's avatar
Lukas Burgey committed
142

Lukas Burgey's avatar
Lukas Burgey committed
143
144
def resource_auth_decision(request, decision):
    user = request.POST.get('username')
145
    permission = request.POST.get('permission', [])
Lukas Burgey's avatar
Lukas Burgey committed
146
147
    resource = request.POST.get('resource', '')
    name = request.POST.get('name', '')
Lukas Burgey's avatar
Lukas Burgey committed
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
    if settings.DEBUG_AUTH:
        if decision == ALLOW:
            LOGGER.debug(
                "[resource] ALLOW %s %s '%s' for %s",
                permission,
                resource,
                name,
                user,
            )
        else:
            LOGGER.error(
                "[recource] DENY %s %s '%s' for %s",
                permission,
                resource,
                name,
                user,
            )
Lukas Burgey's avatar
Lukas Burgey committed
165
    return decision
166

Lukas Burgey's avatar
Lukas Burgey committed
167

Lukas Burgey's avatar
Lukas Burgey committed
168
def resource_endpoint(request):
169
170
171
172
173
    if _valid_vhost(request):
        if (
                _webpage_client_userid(request)
                and _resource_authorized_webpage_client(request)
        ):
Lukas Burgey's avatar
Lukas Burgey committed
174
            return resource_auth_decision(request, ALLOW)
175
176
177
178
179

        if (
                _apiclient_valid(request)
                and _resource_authorized_apiclient(request)
        ):
Lukas Burgey's avatar
Lukas Burgey committed
180
            return resource_auth_decision(request, ALLOW)
181

Lukas Burgey's avatar
Lukas Burgey committed
182
    return resource_auth_decision(request, DENY)
183

Lukas Burgey's avatar
Lukas Burgey committed
184

Lukas Burgey's avatar
Lukas Burgey committed
185
186
def topic_auth_decision(request, decision):
    user = request.POST.get('username')
187
188
189
190
191
    permission = request.POST.get('permission', [])
    resource = request.POST.get('resource', '')
    name = request.POST.get('name', '')
    routing_key = request.POST.get('routing_key', '')

Lukas Burgey's avatar
Lukas Burgey committed
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
    if settings.DEBUG_AUTH:
        if decision == ALLOW:
            LOGGER.debug(
                "[topic] ALLOW %s %s %s '%s' for %s",
                permission,
                resource,
                name,
                routing_key,
                user,
            )
        else:
            LOGGER.error(
                "[topic] DENY %s %s %s '%s' for %s",
                permission,
                resource,
                name,
                routing_key,
                user,
            )
Lukas Burgey's avatar
Lukas Burgey committed
211
212
    return decision

Lukas Burgey's avatar
Lukas Burgey committed
213

Lukas Burgey's avatar
Lukas Burgey committed
214
215
216
217
218
219
def topic_endpoint_webpageclient(request, webpage_client_userid):
    permission = request.POST.get('permission', [])

    try:
        models.User.objects.get(id=webpage_client_userid)

Lukas Burgey's avatar
Lukas Burgey committed
220
        if 'write' not in permission:
Lukas Burgey's avatar
Lukas Burgey committed
221
222
223
224
225
            return topic_auth_decision(request, ALLOW)
        return topic_auth_decision(request, DENY)

    except models.User.DoesNotExist:
        return topic_auth_decision(request, DENY)
226

Lukas Burgey's avatar
Lukas Burgey committed
227

Lukas Burgey's avatar
Lukas Burgey committed
228
229
230
def topic_endpoint_apiclient(request, apiclient):
    name = request.POST.get('name', '')
    routing_key = request.POST.get('routing_key', '')
Lukas Burgey's avatar
Lukas Burgey committed
231

Lukas Burgey's avatar
Lukas Burgey committed
232
    if name == 'services':
Lukas Burgey's avatar
Lukas Burgey committed
233
234
235
        # TODO is this check sufficient?
        if apiclient.site.services.filter(name=routing_key).exists():
            return ALLOW
Lukas Burgey's avatar
Lukas Burgey committed
236
237
    elif name == 'sites':
        if routing_key == apiclient.site.name:
Lukas Burgey's avatar
Lukas Burgey committed
238
239
240
            return topic_auth_decision(request, ALLOW)

        return topic_auth_decision(request, DENY)
Lukas Burgey's avatar
Lukas Burgey committed
241
242
243
    elif name == 'groups':
        try:
            group = Group.objects.get(name=routing_key)
Lukas Burgey's avatar
Lukas Burgey committed
244

Lukas Burgey's avatar
Lukas Burgey committed
245
            try:
Lukas Burgey's avatar
Lukas Burgey committed
246
                models.Site.objects.get(
Lukas Burgey's avatar
Lukas Burgey committed
247
248
249
                    services__groups=group,
                    client=apiclient,
                )
Lukas Burgey's avatar
Lukas Burgey committed
250
                return topic_auth_decision(request, ALLOW)
Lukas Burgey's avatar
Lukas Burgey committed
251

Lukas Burgey's avatar
Lukas Burgey committed
252
            except models.Site.MultipleObjectsReturned:
Lukas Burgey's avatar
Lukas Burgey committed
253
                return topic_auth_decision(request, ALLOW)
Lukas Burgey's avatar
Lukas Burgey committed
254

Lukas Burgey's avatar
Lukas Burgey committed
255
            except models.Site.DoesNotExist:
Lukas Burgey's avatar
Lukas Burgey committed
256
                return topic_auth_decision(request, DENY)
Lukas Burgey's avatar
Lukas Burgey committed
257

Lukas Burgey's avatar
Lukas Burgey committed
258
        except Group.DoesNotExist:
Lukas Burgey's avatar
Lukas Burgey committed
259
            return topic_auth_decision(request, DENY)
Lukas Burgey's avatar
Lukas Burgey committed
260

Lukas Burgey's avatar
Lukas Burgey committed
261
    return topic_auth_decision(request, DENY)
Lukas Burgey's avatar
Lukas Burgey committed
262

Lukas Burgey's avatar
Lukas Burgey committed
263

Lukas Burgey's avatar
Lukas Burgey committed
264
265
266
267
268
def topic_endpoint(request):
    if not _valid_vhost(request) or not _valid_permission(request):
        return DENY

    webpage_client_userid = _webpage_client_userid(request)
Lukas Burgey's avatar
Lukas Burgey committed
269
    if webpage_client_userid != '':
Lukas Burgey's avatar
Lukas Burgey committed
270
271
272
273
274
275
        return topic_endpoint_webpageclient(request, webpage_client_userid)

    apiclient = _apiclient_get(request)
    if apiclient:
        return topic_endpoint_apiclient(request, apiclient)

Lukas Burgey's avatar
Lukas Burgey committed
276
    return topic_auth_decision(request, DENY)