From 40d563626e7da8c2b1faa884caeb2a466b1e58d8 Mon Sep 17 00:00:00 2001 From: adamscmRH Date: Tue, 27 Feb 2018 13:23:45 -0500 Subject: [PATCH] removes authtoken --- awx/api/serializers.py | 21 --- .../migrations/0025_v330_delete_authtoken.py | 25 +++ awx/main/models/organization.py | 147 +----------------- awx/main/tests/base.py | 2 - awx/main/tests/unit/api/test_filters.py | 4 +- .../login/loginModal/loginModal.controller.js | 15 -- 6 files changed, 29 insertions(+), 185 deletions(-) create mode 100644 awx/main/migrations/0025_v330_delete_authtoken.py diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 054d0573dd..76399faebd 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -17,7 +17,6 @@ from oauth2_provider.settings import oauth2_settings # Django from django.conf import settings -from django.contrib.auth import authenticate from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist, ValidationError as DjangoValidationError @@ -4664,26 +4663,6 @@ class ActivityStreamSerializer(BaseSerializer): return summary_fields -class AuthTokenSerializer(serializers.Serializer): - - username = serializers.CharField() - password = serializers.CharField() - - def validate(self, attrs): - username = attrs.get('username') - password = attrs.get('password') - - if username and password: - user = authenticate(username=username, password=password) - if user: - attrs['user'] = user - return attrs - else: - raise serializers.ValidationError(_('Unable to login with provided credentials.')) - else: - raise serializers.ValidationError(_('Must include "username" and "password".')) - - class FactVersionSerializer(BaseFactSerializer): class Meta: diff --git a/awx/main/migrations/0025_v330_delete_authtoken.py b/awx/main/migrations/0025_v330_delete_authtoken.py new file mode 100644 index 0000000000..cd55a901b1 --- /dev/null +++ b/awx/main/migrations/0025_v330_delete_authtoken.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2018-02-27 17:58 +from __future__ import unicode_literals + +import awx.main.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0024_v330_add_oauth_activity_stream_registrar'), + ] + + operations = [ + migrations.RemoveField( + model_name='authtoken', + name='user', + ), + migrations.DeleteModel( + name='AuthToken', + ), + ] diff --git a/awx/main/models/organization.py b/awx/main/models/organization.py index 8ec785a0c8..34a17919fc 100644 --- a/awx/main/models/organization.py +++ b/awx/main/models/organization.py @@ -1,21 +1,15 @@ # Copyright (c) 2015 Ansible, Inc. # All Rights Reserved. -# Python -import datetime -import hashlib -import hmac -import uuid + # Django from django.conf import settings -from django.db import models, connection +from django.db import models from django.contrib.auth.models import User from django.contrib.sessions.models import Session from django.utils.timezone import now as tz_now -from django.utils.translation import ugettext_lazy as _ -import six # AWX from awx.api.versioning import reverse @@ -27,7 +21,7 @@ from awx.main.models.rbac import ( ) from awx.main.models.mixins import ResourceMixin, CustomVirtualEnvMixin -__all__ = ['Organization', 'Team', 'Profile', 'AuthToken', 'UserSessionMembership'] +__all__ = ['Organization', 'Team', 'Profile', 'UserSessionMembership'] class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVirtualEnvMixin): @@ -135,141 +129,6 @@ class Profile(CreatedModifiedModel): ) -""" -Since expiration and session expiration is event driven a token could be -invalidated for both reasons. Further, we only support a single reason for a -session token being invalid. For this case, mark the token as expired. - -Note: Again, because the value of reason is event based. The reason may not be -set (i.e. may equal '') even though a session is expired or a limit is reached. -""" - - -class AuthToken(BaseModel): - ''' - Custom authentication tokens per user with expiration and request-specific - data. - ''' - - REASON_CHOICES = [ - ('', _('Token not invalidated')), - ('timeout_reached', _('Token is expired')), - ('limit_reached', _('The maximum number of allowed sessions for this user has been exceeded.')), - # invalid_token is not a used data-base value, but is returned by the - # api when a token is not found - ('invalid_token', _('Invalid token')), - ] - - class Meta: - app_label = 'main' - - key = models.CharField(max_length=40, primary_key=True) - user = prevent_search(models.ForeignKey('auth.User', - related_name='auth_tokens', on_delete=models.CASCADE)) - created = models.DateTimeField(auto_now_add=True) - modified = models.DateTimeField(auto_now=True) - expires = models.DateTimeField(default=tz_now) - request_hash = prevent_search(models.CharField(max_length=40, blank=True, - default='')) - reason = models.CharField( - max_length=1024, - blank=True, - default='', - help_text=_('Reason the auth token was invalidated.') - ) - - @staticmethod - def reason_long(reason): - for x in AuthToken.REASON_CHOICES: - if x[0] == reason: - return six.text_type(x[1]) - return None - - @classmethod - def get_request_hash(cls, request): - h = hashlib.sha1() - h.update(settings.SECRET_KEY) - for header in settings.REMOTE_HOST_HEADERS: - value = request.META.get(header, '').split(',')[0].strip() - if value: - h.update(value) - break - h.update(request.META.get('HTTP_USER_AGENT', '')) - return h.hexdigest() - - def save(self, *args, **kwargs): - if not self.pk: - self.refresh(save=False) - if not self.key: - self.key = self.generate_key() - return super(AuthToken, self).save(*args, **kwargs) - - def refresh(self, now=None, save=True): - if not now: - now = tz_now() - if not self.pk or not self.is_expired(now=now): - self.expires = now + datetime.timedelta(seconds=settings.AUTH_TOKEN_EXPIRATION) - if save: - connection.on_commit(lambda: self.save(update_fields=['expires'])) - - def invalidate(self, reason='timeout_reached', save=True): - if not AuthToken.reason_long(reason): - raise ValueError(_('Invalid reason specified')) - self.reason = reason - if save: - self.save() - return reason - - @staticmethod - def get_tokens_over_limit(user, now=None): - if now is None: - now = tz_now() - invalid_tokens = AuthToken.objects.none() - if settings.AUTH_TOKEN_PER_USER != -1: - invalid_tokens = AuthToken.objects.filter( - user=user, - expires__gt=now, - reason='', - ).order_by('-created')[settings.AUTH_TOKEN_PER_USER:] - return invalid_tokens - - def generate_key(self): - unique = uuid.uuid4() - return hmac.new(unique.bytes, digestmod=hashlib.sha1).hexdigest() - - def is_expired(self, now=None): - if not now: - now = tz_now() - return bool(self.expires < now) - - @property - def invalidated(self): - return bool(self.reason != '') - - """ - Token is valid if it's in the set of unexpired tokens. - The unexpired token set is: - * tokens not expired - * limited to number of tokens per-user - * sorted by created on date - """ - def in_valid_tokens(self, now=None): - if not now: - now = tz_now() - valid_n_tokens_qs = self.user.auth_tokens.filter( - expires__gt=now, - reason='', - ).order_by('-created') - if settings.AUTH_TOKEN_PER_USER != -1: - valid_n_tokens_qs = valid_n_tokens_qs[0:settings.AUTH_TOKEN_PER_USER] - valid_n_tokens = valid_n_tokens_qs.values_list('key', flat=True) - - return bool(self.key in valid_n_tokens) - - def __unicode__(self): - return self.key - - class UserSessionMembership(BaseModel): ''' A lookup table for session membership given user. diff --git a/awx/main/tests/base.py b/awx/main/tests/base.py index 14863bda6a..8535d0c4ea 100644 --- a/awx/main/tests/base.py +++ b/awx/main/tests/base.py @@ -205,8 +205,6 @@ class BaseTestMixin(MockCommonlySlowTestMixin): user = User.objects.create_superuser(username, "%s@example.com", password) else: user = User.objects.create_user(username, "%s@example.com", password) - # New user should have no auth tokens by default. - self.assertFalse(user.auth_tokens.count()) self._user_passwords[user.username] = password return user diff --git a/awx/main/tests/unit/api/test_filters.py b/awx/main/tests/unit/api/test_filters.py index 1b8007a516..c0dcf35299 100644 --- a/awx/main/tests/unit/api/test_filters.py +++ b/awx/main/tests/unit/api/test_filters.py @@ -4,7 +4,7 @@ import pytest from rest_framework.exceptions import PermissionDenied, ParseError from awx.api.filters import FieldLookupBackend -from awx.main.models import (AdHocCommand, AuthToken, CustomInventoryScript, +from awx.main.models import (AdHocCommand, CustomInventoryScript, Credential, Job, JobTemplate, SystemJob, UnifiedJob, User, WorkflowJob, WorkflowJobTemplate, WorkflowJobOptions, @@ -54,9 +54,7 @@ def test_filter_on_password_field(password_field, lookup_suffix): @pytest.mark.parametrize('model, query', [ - (AuthToken, 'request_hash__icontains'), (User, 'password__icontains'), - (User, 'auth_tokens__key__icontains'), (User, 'settings__value__icontains'), (UnifiedJob, 'job_args__icontains'), (UnifiedJob, 'job_env__icontains'), diff --git a/awx/ui/client/src/login/loginModal/loginModal.controller.js b/awx/ui/client/src/login/loginModal/loginModal.controller.js index 98c9350155..a63963c2bc 100644 --- a/awx/ui/client/src/login/loginModal/loginModal.controller.js +++ b/awx/ui/client/src/login/loginModal/loginModal.controller.js @@ -34,21 +34,6 @@ * Just before the release of 2.0 a bug was discovered where clicking logout and then immediately clicking login without providing a username and password would successfully log * the user back into the app. Implementing the above approach fixed this, forcing a new username/password to be entered each time the login dialog appears. * - * #Login Workflow - * - * When the the login button is clicked, the following occurs: - * - * - Call Authorization.retrieveToken(username, password) - sends a POST request to /api/v2/authtoken to get a new token value. - * - Call Authorization.setToken(token, expires) to store the token and exipration time in a session cookie. - * - Start the expiration timer by calling the init() method of [js/shared/Timer.js](/static/docs/api/shared.function:Timer) - * - Get user informaton by calling Authorization.getUser() - sends a GET request to /api/v2/me - * - Store user information in the session cookie by calling Authorization.setUser(). - * - Get the license by calling ConfigService.getConfig() - sends a GET request to /api/vi/config - * - Stores the license object in memory by calling CheckLicense.test(). This adds the version and a tested flag to the license object. The tested flag is initially set to false. Additionally, the pendoService and FeaturesService are called to initiate the other startup services - * - * Note that there is a session timer kept on the server side as well as the client side. Each time an API request is made, app.js calls - * Timer.isExpired(). This verifies the UI does not think the session is expired, and if not, moves the expiration time into the future. The number of - * seconds between API calls before a session is considered expired is set in config.js as session_timeout. * * @Usage * This is usage information.