Merge pull request #1435 from anoek/user-activity-stream-updates

Added activity stream events for User
This commit is contained in:
Akita Noek
2016-04-12 09:10:52 -04:00
6 changed files with 52 additions and 13 deletions

View File

@@ -3,9 +3,11 @@
# Python # Python
import urllib import urllib
import logging
# Django # Django
from django.utils.timezone import now as tz_now from django.utils.timezone import now as tz_now
from django.utils.encoding import smart_text
# Django REST Framework # Django REST Framework
from rest_framework import authentication from rest_framework import authentication
@@ -16,6 +18,8 @@ from rest_framework import HTTP_HEADER_ENCODING
from awx.main.models import UnifiedJob, AuthToken from awx.main.models import UnifiedJob, AuthToken
from awx.main.conf import tower_settings from awx.main.conf import tower_settings
logger = logging.getLogger('awx.api.authentication')
class TokenAuthentication(authentication.TokenAuthentication): class TokenAuthentication(authentication.TokenAuthentication):
''' '''
Custom token authentication using tokens that expire and are associated Custom token authentication using tokens that expire and are associated
@@ -116,6 +120,16 @@ class TokenGetAuthentication(TokenAuthentication):
return super(TokenGetAuthentication, self).authenticate(request) return super(TokenGetAuthentication, self).authenticate(request)
class LoggedBasicAuthentication(authentication.BasicAuthentication):
def authenticate(self, request):
ret = super(LoggedBasicAuthentication, self).authenticate(request)
if ret:
username = ret[0].username if ret[0] else '<none>'
logger.debug(smart_text(u"User {} performed a {} to {} through the API".format(username, request.method, request.path)))
return ret
class TaskAuthentication(authentication.BaseAuthentication): class TaskAuthentication(authentication.BaseAuthentication):
''' '''
Custom authentication used for views accessed by the inventory and callback Custom authentication used for views accessed by the inventory and callback

View File

@@ -11,6 +11,7 @@ import time
import socket import socket
import sys import sys
import errno import errno
import logging
from base64 import b64encode from base64 import b64encode
from collections import OrderedDict from collections import OrderedDict
@@ -22,7 +23,7 @@ from django.core.exceptions import FieldError
from django.db.models import Q, Count from django.db.models import Q, Count
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.encoding import force_text from django.utils.encoding import smart_text, force_text
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.timezone import now from django.utils.timezone import now
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
@@ -71,6 +72,8 @@ from awx.api.metadata import RoleMetadata
from awx.main.utils import emit_websocket_notification from awx.main.utils import emit_websocket_notification
from awx.main.conf import tower_settings from awx.main.conf import tower_settings
logger = logging.getLogger('awx.api.views')
def api_exception_handler(exc, context): def api_exception_handler(exc, context):
''' '''
Override default API exception handler to catch IntegrityError exceptions. Override default API exception handler to catch IntegrityError exceptions.
@@ -528,9 +531,13 @@ class AuthTokenView(APIView):
expires__gt=now(), expires__gt=now(),
reason='')[0] reason='')[0]
token.refresh() token.refresh()
if 'username' in request.data:
logger.info(smart_text(u"User {} logged in".format(request.data['username'])))
except IndexError: except IndexError:
token = AuthToken.objects.create(user=serializer.validated_data['user'], token = AuthToken.objects.create(user=serializer.validated_data['user'],
request_hash=request_hash) request_hash=request_hash)
if 'username' in request.data:
logger.info(smart_text(u"User {} logged in".format(request.data['username'])))
# Get user un-expired tokens that are not invalidated that are # Get user un-expired tokens that are not invalidated that are
# over the configured limit. # over the configured limit.
# Mark them as invalid and inform the user # Mark them as invalid and inform the user
@@ -549,6 +556,8 @@ class AuthTokenView(APIView):
'Auth-Token-Timeout': int(tower_settings.AUTH_TOKEN_EXPIRATION) 'Auth-Token-Timeout': int(tower_settings.AUTH_TOKEN_EXPIRATION)
} }
return Response({'token': token.key, 'expires': token.expires}, headers=headers) return Response({'token': token.key, 'expires': token.expires}, headers=headers)
if 'username' in request.data:
logger.warning(smart_text(u"Login failed for user {}".format(request.data['username'])))
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class OrganizationList(ListCreateAPIView): class OrganizationList(ListCreateAPIView):

View File

@@ -85,3 +85,4 @@ activity_stream_registrar.connect(TowerSettings)
activity_stream_registrar.connect(Notifier) activity_stream_registrar.connect(Notifier)
activity_stream_registrar.connect(Notification) activity_stream_registrar.connect(Notification)
activity_stream_registrar.connect(Label) activity_stream_registrar.connect(Label)
activity_stream_registrar.connect(User)

View File

@@ -284,6 +284,22 @@ def update_scm_url(scm_type, url, username=True, password=True,
return new_url return new_url
def get_allowed_fields(obj, serializer_mapping):
from django.contrib.auth.models import User
if serializer_mapping is not None and obj.__class__ in serializer_mapping:
serializer_actual = serializer_mapping[obj.__class__]()
allowed_fields = [x for x in serializer_actual.fields if not serializer_actual.fields[x].read_only] + ['id']
else:
allowed_fields = [x.name for x in obj._meta.fields]
if isinstance(obj, User):
field_blacklist = ['last_login']
allowed_fields = [f for f in allowed_fields if f not in field_blacklist]
return allowed_fields
def model_instance_diff(old, new, serializer_mapping=None): def model_instance_diff(old, new, serializer_mapping=None):
""" """
Calculate the differences between two model instances. One of the instances may be None (i.e., a newly Calculate the differences between two model instances. One of the instances may be None (i.e., a newly
@@ -301,11 +317,7 @@ def model_instance_diff(old, new, serializer_mapping=None):
diff = {} diff = {}
if serializer_mapping is not None and new.__class__ in serializer_mapping: allowed_fields = get_allowed_fields(new, serializer_mapping)
serializer_actual = serializer_mapping[new.__class__]()
allowed_fields = [x for x in serializer_actual.fields if not serializer_actual.fields[x].read_only] + ['id']
else:
allowed_fields = [x.name for x in new._meta.fields]
for field in allowed_fields: for field in allowed_fields:
old_value = getattr(old, field, None) old_value = getattr(old, field, None)
@@ -334,11 +346,9 @@ def model_to_dict(obj, serializer_mapping=None):
""" """
from awx.main.models.credential import Credential from awx.main.models.credential import Credential
attr_d = {} attr_d = {}
if serializer_mapping is not None and obj.__class__ in serializer_mapping:
serializer_actual = serializer_mapping[obj.__class__]() allowed_fields = get_allowed_fields(obj, serializer_mapping)
allowed_fields = [x for x in serializer_actual.fields if not serializer_actual.fields[x].read_only] + ['id']
else:
allowed_fields = [x.name for x in obj._meta.fields]
for field in obj._meta.fields: for field in obj._meta.fields:
if field.name not in allowed_fields: if field.name not in allowed_fields:
continue continue

View File

@@ -202,7 +202,7 @@ REST_FRAMEWORK = {
'PAGE_SIZE': 25, 'PAGE_SIZE': 25,
'DEFAULT_AUTHENTICATION_CLASSES': ( 'DEFAULT_AUTHENTICATION_CLASSES': (
'awx.api.authentication.TokenAuthentication', 'awx.api.authentication.TokenAuthentication',
'rest_framework.authentication.BasicAuthentication', 'awx.api.authentication.LoggedBasicAuthentication',
#'rest_framework.authentication.SessionAuthentication', #'rest_framework.authentication.SessionAuthentication',
), ),
'DEFAULT_PERMISSION_CLASSES': ( 'DEFAULT_PERMISSION_CLASSES': (

View File

@@ -3,6 +3,7 @@
# Python # Python
import urllib import urllib
import logging
# Django # Django
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@@ -10,6 +11,7 @@ from django.http import HttpResponse
from django.utils.timezone import now, utc from django.utils.timezone import now, utc
from django.views.generic import View from django.views.generic import View
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.utils.encoding import smart_text
# Django REST Framework # Django REST Framework
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
@@ -18,6 +20,7 @@ from rest_framework.renderers import JSONRenderer
from awx.main.models import AuthToken from awx.main.models import AuthToken
from awx.api.serializers import UserSerializer from awx.api.serializers import UserSerializer
logger = logging.getLogger('awx.sso.views')
class BaseRedirectView(RedirectView): class BaseRedirectView(RedirectView):
@@ -45,9 +48,11 @@ class CompleteView(BaseRedirectView):
request_hash=request_hash, request_hash=request_hash,
expires__gt=now())[0] expires__gt=now())[0]
token.refresh() token.refresh()
logger.info(smart_text(u"User {} logged in".format(self.request.user.username)))
except IndexError: except IndexError:
token = AuthToken.objects.create(user=request.user, token = AuthToken.objects.create(user=request.user,
request_hash=request_hash) request_hash=request_hash)
logger.info(smart_text(u"User {} logged in".format(self.request.user.username)))
request.session['auth_token_key'] = token.key request.session['auth_token_key'] = token.key
token_key = urllib.quote('"%s"' % token.key) token_key = urllib.quote('"%s"' % token.key)
response.set_cookie('token', token_key) response.set_cookie('token', token_key)