From 36c06020b440d0da7ec284d18dcfd3822e4a9eb7 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Tue, 31 Jan 2017 20:19:45 -0500 Subject: [PATCH] Get users from the http_session, authorize job, workflow, and adhoc event access against RBAC --- awx/main/access.py | 13 ++++++++- awx/main/consumers.py | 61 ++++++++++++++++--------------------------- 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index 7bef51a20f..9976a359c5 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -25,7 +25,7 @@ from awx.main.task_engine import TaskEnhancer from awx.conf.license import LicenseForbids __all__ = ['get_user_queryset', 'check_user_access', 'check_user_access_with_errors', - 'user_accessible_objects', + 'user_accessible_objects', 'consumer_access', 'user_admin_role', 'StateConflict',] PERMISSION_TYPES = [ @@ -164,6 +164,17 @@ def check_superuser(func): return wrapper +def consumer_access(group_name): + ''' + consumer_access returns the proper Access class based on group_name + for a channels consumer. + ''' + class_map = {'job_events': JobAccess, + 'workflow_events': WorkflowJobAccess, + 'ad_hoc_command_events': AdHocCommandAccess} + return class_map.get(group_name) + + class BaseAccess(object): ''' Base class for checking user access to a given model. Subclasses should diff --git a/awx/main/consumers.py b/awx/main/consumers.py index 2cb1f450f2..ecd2b84eff 100644 --- a/awx/main/consumers.py +++ b/awx/main/consumers.py @@ -1,13 +1,11 @@ import json -import urlparse import logging from channels import Group from channels.sessions import channel_session +from channels.auth import channel_session_user, http_session_user, transfer_user -from django.contrib.auth.models import User from django.core.serializers.json import DjangoJSONEncoder -from awx.main.models.organization import AuthToken logger = logging.getLogger('awx.main.consumers') @@ -19,51 +17,32 @@ def discard_groups(message): Group(group).discard(message.reply_channel) -def validate_token(token): - try: - auth_token = AuthToken.objects.get(key=token) - if not auth_token.in_valid_tokens: - return None - except AuthToken.DoesNotExist: - return None - return auth_token - - -def user_from_token(auth_token): - try: - return User.objects.get(pk=auth_token.user_id) - except User.DoesNotExist: - return None - - +@http_session_user @channel_session def ws_connect(message): - token = None - qs = urlparse.parse_qs(message['query_string']) - if 'token' in qs: - if len(qs['token']) > 0: - token = qs['token'].pop() - message.channel_session['token'] = token + if message.http_session: + # our backend is not set on the http_session so we need to update the session before + # calling transfer_user. This is why we are doing this manually instead of using the + # all-in-one helper decorator. + message.http_session.update({'_auth_user_backend':'django.contrib.auth.backends.ModelBackend'}) + transfer_user(message.http_session, message.channel_session) + message.reply_channel.send({"text": json.dumps({"accept": True, "user": message.user.id})}) + else: + message.reply_channel.send({"text": json.dumps({"accept": False, "user": None})}) -@channel_session +@channel_session_user def ws_disconnect(message): discard_groups(message) -@channel_session +@channel_session_user def ws_receive(message): - token = message.channel_session.get('token') + from awx.main.access import consumer_access - auth_token = validate_token(token) - if auth_token is None: - logger.error("Authentication Failure validating user") - message.reply_channel.send({"text": json.dumps({"error": "invalid auth token"})}) - return None - - user = user_from_token(auth_token) - if user is None: - logger.error("No valid user corresponding to submitted auth_token") + user = message.user + if user.id is None: + logger.error("No valid user found for websocket.") message.reply_channel.send({"text": json.dumps({"error": "no valid user"})}) return None @@ -78,6 +57,12 @@ def ws_receive(message): if type(v) is list: for oid in v: name = '{}-{}'.format(group_name, oid) + access_cls = consumer_access(group_name) + if access_cls is not None: + user_access = access_cls(user) + if not user_access.get_queryset().filter(pk=oid).exists(): + message.reply_channel.send({"text": json.dumps({"error": "access denied to channel {0} for resource id {1}".format(group_name, oid)})}) + continue current_groups.append(name) Group(name).add(message.reply_channel) else: