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)