mirror of
https://github.com/ansible/awx.git
synced 2026-05-08 09:57:35 -02:30
Merge pull request #446 from chrismeyersfsu/feature-session_limit2
Feature session limit2
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
# Copyright (c) 2015 Ansible, Inc.
|
# Copyright (c) 2015 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
# Django
|
||||||
|
from django.utils.timezone import now as tz_now
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework import authentication
|
from rest_framework import authentication
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
@@ -48,6 +52,7 @@ class TokenAuthentication(authentication.TokenAuthentication):
|
|||||||
return self.authenticate_credentials(auth[1])
|
return self.authenticate_credentials(auth[1])
|
||||||
|
|
||||||
def authenticate_credentials(self, key):
|
def authenticate_credentials(self, key):
|
||||||
|
now = tz_now()
|
||||||
# Retrieve the request hash and token.
|
# Retrieve the request hash and token.
|
||||||
try:
|
try:
|
||||||
request_hash = self.model.get_request_hash(self.request)
|
request_hash = self.model.get_request_hash(self.request)
|
||||||
@@ -56,21 +61,31 @@ class TokenAuthentication(authentication.TokenAuthentication):
|
|||||||
request_hash=request_hash,
|
request_hash=request_hash,
|
||||||
)
|
)
|
||||||
except self.model.DoesNotExist:
|
except self.model.DoesNotExist:
|
||||||
raise exceptions.AuthenticationFailed('Invalid token')
|
raise exceptions.AuthenticationFailed(AuthToken.reason_long('invalid_token'))
|
||||||
|
|
||||||
# Sanity check: Ensure that the token is still valid.
|
# Tell the user why their token was previously invalidated.
|
||||||
# Tokens expire if they are not used for 30 minutes.
|
if token.invalidated:
|
||||||
if token.expired:
|
raise exceptions.AuthenticationFailed(AuthToken.reason_long(token.reason))
|
||||||
raise exceptions.AuthenticationFailed('Token is expired')
|
|
||||||
|
|
||||||
# Sanity check: If the user is inactive, then return an error.
|
# Explicitly handle expired tokens
|
||||||
|
if token.is_expired(now=now):
|
||||||
|
token.invalidate(reason='timeout_reached')
|
||||||
|
raise exceptions.AuthenticationFailed(AuthToken.reason_long('timeout_reached'))
|
||||||
|
|
||||||
|
# Token invalidated due to session limit config being reduced
|
||||||
|
# Session limit reached invalidation will also take place on authentication
|
||||||
|
if settings.AUTH_TOKEN_PER_USER != -1:
|
||||||
|
if not token.in_valid_tokens(now=now):
|
||||||
|
token.invalidate(reason='limit_reached')
|
||||||
|
raise exceptions.AuthenticationFailed(AuthToken.reason_long('limit_reached'))
|
||||||
|
|
||||||
|
# If the user is inactive, then return an error.
|
||||||
if not token.user.is_active:
|
if not token.user.is_active:
|
||||||
raise exceptions.AuthenticationFailed('User inactive or deleted')
|
raise exceptions.AuthenticationFailed('User inactive or deleted')
|
||||||
|
|
||||||
# Refresh the token.
|
# Refresh the token.
|
||||||
# This updates the time that the token was last used, meaning that
|
# The token is extended from "right now" + configurable setting amount.
|
||||||
# now the token is valid for 30 minutes from "right now".
|
token.refresh(now=now)
|
||||||
token.refresh()
|
|
||||||
|
|
||||||
# Return the user object and the token.
|
# Return the user object and the token.
|
||||||
return (token.user, token)
|
return (token.user, token)
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ from awx.api.permissions import * # noqa
|
|||||||
from awx.api.renderers import * # noqa
|
from awx.api.renderers import * # noqa
|
||||||
from awx.api.serializers import * # noqa
|
from awx.api.serializers import * # noqa
|
||||||
from awx.fact.models import * # noqa
|
from awx.fact.models import * # noqa
|
||||||
|
from awx.main.utils import emit_websocket_notification
|
||||||
|
|
||||||
def api_exception_handler(exc):
|
def api_exception_handler(exc):
|
||||||
'''
|
'''
|
||||||
@@ -527,12 +528,30 @@ class AuthTokenView(APIView):
|
|||||||
try:
|
try:
|
||||||
token = AuthToken.objects.filter(user=serializer.object['user'],
|
token = AuthToken.objects.filter(user=serializer.object['user'],
|
||||||
request_hash=request_hash,
|
request_hash=request_hash,
|
||||||
expires__gt=now())[0]
|
expires__gt=now(),
|
||||||
|
reason='')[0]
|
||||||
token.refresh()
|
token.refresh()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
token = AuthToken.objects.create(user=serializer.object['user'],
|
token = AuthToken.objects.create(user=serializer.object['user'],
|
||||||
request_hash=request_hash)
|
request_hash=request_hash)
|
||||||
return Response({'token': token.key, 'expires': token.expires})
|
# Get user un-expired tokens that are not invalidated that are
|
||||||
|
# over the configured limit.
|
||||||
|
# Mark them as invalid and inform the user
|
||||||
|
invalid_tokens = AuthToken.get_tokens_over_limit(serializer.object['user'])
|
||||||
|
for t in invalid_tokens:
|
||||||
|
# TODO: send socket notification
|
||||||
|
emit_websocket_notification('/socket.io/control',
|
||||||
|
'limit_reached',
|
||||||
|
dict(reason=unicode(AuthToken.reason_long('limit_reached'))),
|
||||||
|
token_key=t.key)
|
||||||
|
t.invalidate(reason='limit_reached')
|
||||||
|
|
||||||
|
# Note: This header is normally added in the middleware whenever an
|
||||||
|
# auth token is included in the request header.
|
||||||
|
headers = {
|
||||||
|
'Auth-Token-Timeout': int(settings.AUTH_TOKEN_EXPIRATION)
|
||||||
|
}
|
||||||
|
return Response({'token': token.key, 'expires': token.expires}, headers=headers)
|
||||||
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):
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import urllib
|
import urllib
|
||||||
|
import weakref
|
||||||
from optparse import make_option
|
from optparse import make_option
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
@@ -24,51 +25,139 @@ from socketio.namespace import BaseNamespace
|
|||||||
|
|
||||||
logger = logging.getLogger('awx.main.commands.run_socketio_service')
|
logger = logging.getLogger('awx.main.commands.run_socketio_service')
|
||||||
|
|
||||||
valid_sockets = []
|
class SocketSession(object):
|
||||||
|
def __init__(self, session_id, token_key, socket):
|
||||||
|
self.socket = weakref.ref(socket)
|
||||||
|
self.session_id = session_id
|
||||||
|
self.token_key = token_key
|
||||||
|
self._valid = True
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
return bool(self._valid)
|
||||||
|
|
||||||
|
def invalidate(self):
|
||||||
|
self._valid = False
|
||||||
|
|
||||||
|
def is_db_token_valid(self):
|
||||||
|
auth_token = AuthToken.objects.filter(key=self.token_key, reason='')
|
||||||
|
if not auth_token.exists():
|
||||||
|
return False
|
||||||
|
auth_token = auth_token[0]
|
||||||
|
return bool(not auth_token.is_expired())
|
||||||
|
|
||||||
|
class SocketSessionManager(object):
|
||||||
|
sessions = []
|
||||||
|
session_token_key_map = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _prune(cls):
|
||||||
|
#cls.sessions = [s for s in cls.sessions if s.is_valid()]
|
||||||
|
if len(cls.sessions) > 1000:
|
||||||
|
del cls.session_token_key_map[cls.sessions[0].token_key]
|
||||||
|
cls.sessions = cls.sessions[1:]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def lookup(cls, token_key=None):
|
||||||
|
if not token_key:
|
||||||
|
raise ValueError("token_key required")
|
||||||
|
for s in cls.sessions:
|
||||||
|
if s.token_key == token_key:
|
||||||
|
return s
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_session(cls, session):
|
||||||
|
cls.sessions.append(session)
|
||||||
|
cls.session_token_key_map[session.token_key] = session
|
||||||
|
cls._prune()
|
||||||
|
|
||||||
|
class SocketController(object):
|
||||||
|
server = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def broadcast_packet(cls, packet):
|
||||||
|
# Broadcast message to everyone at endpoint
|
||||||
|
# Loop over the 'raw' list of sockets (don't trust our list)
|
||||||
|
for session_id, socket in list(cls.server.sockets.iteritems()):
|
||||||
|
socket_session = socket.session.get('socket_session', None)
|
||||||
|
if socket_session and socket_session.is_valid():
|
||||||
|
try:
|
||||||
|
socket.send_packet(packet)
|
||||||
|
except Exception, e:
|
||||||
|
logger.error("Error sending client packet to %s: %s" % (str(session_id), str(packet)))
|
||||||
|
logger.error("Error was: " + str(e))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def send_packet(cls, packet, token_key):
|
||||||
|
if not token_key:
|
||||||
|
raise ValueError("token_key is required")
|
||||||
|
socket_session = SocketSessionManager.lookup(token_key=token_key)
|
||||||
|
# We may not find the socket_session if the user disconnected
|
||||||
|
# (it's actually more compliciated than that because of our prune logic)
|
||||||
|
if socket_session and socket_session.is_valid():
|
||||||
|
socket = socket_session.socket()
|
||||||
|
if socket:
|
||||||
|
try:
|
||||||
|
socket.send_packet(packet)
|
||||||
|
except Exception, e:
|
||||||
|
logger.error("Error sending client packet to %s: %s" % (str(socket_session.session_id), str(packet)))
|
||||||
|
logger.error("Error was: " + str(e))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_server(cls, server):
|
||||||
|
cls.server = server
|
||||||
|
return server
|
||||||
|
|
||||||
|
#
|
||||||
|
# Socket session is attached to self.session['socket_session']
|
||||||
|
# self.session and self.socket.session point to the same dict
|
||||||
|
#
|
||||||
class TowerBaseNamespace(BaseNamespace):
|
class TowerBaseNamespace(BaseNamespace):
|
||||||
|
|
||||||
def get_allowed_methods(self):
|
def get_allowed_methods(self):
|
||||||
return ['recv_disconnect']
|
return ['recv_disconnect']
|
||||||
|
|
||||||
def get_initial_acl(self):
|
def get_initial_acl(self):
|
||||||
global valid_sockets
|
request_token = self._get_request_token()
|
||||||
v_user = self.valid_user()
|
if request_token:
|
||||||
self.is_valid_connection = False
|
# (1) This is the first time the socket has been seen (first
|
||||||
if v_user:
|
# namespace joined).
|
||||||
if self.socket.sessid not in valid_sockets:
|
# (2) This socket has already been seen (already joined and maybe
|
||||||
valid_sockets.append(self.socket.sessid)
|
# left a namespace)
|
||||||
self.is_valid_connection = True
|
#
|
||||||
if len(valid_sockets) > 1000:
|
# Note: Assume that the user token is valid if the session is found
|
||||||
valid_sockets = valid_sockets[1:]
|
socket_session = self.session.get('socket_session', None)
|
||||||
|
if not socket_session:
|
||||||
|
socket_session = SocketSession(self.socket.sessid, request_token, self.socket)
|
||||||
|
if socket_session.is_db_token_valid():
|
||||||
|
self.session['socket_session'] = socket_session
|
||||||
|
SocketSessionManager.add_session(socket_session)
|
||||||
|
else:
|
||||||
|
socket_session.invalidate()
|
||||||
|
|
||||||
return set(['recv_connect'] + self.get_allowed_methods())
|
return set(['recv_connect'] + self.get_allowed_methods())
|
||||||
else:
|
else:
|
||||||
logger.warn("Authentication Failure validating user")
|
logger.warn("Authentication Failure validating user")
|
||||||
self.emit("connect_failed", "Authentication failed")
|
self.emit("connect_failed", "Authentication failed")
|
||||||
return set(['recv_connect'])
|
return set(['recv_connect'])
|
||||||
|
|
||||||
def valid_user(self):
|
def _get_request_token(self):
|
||||||
if 'QUERY_STRING' not in self.environ:
|
if 'QUERY_STRING' not in self.environ:
|
||||||
return False
|
return False
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
k, v = self.environ['QUERY_STRING'].split("=")
|
k, v = self.environ['QUERY_STRING'].split("=")
|
||||||
if k == "Token":
|
if k == "Token":
|
||||||
token_actual = urllib.unquote_plus(v).decode().replace("\"","")
|
token_actual = urllib.unquote_plus(v).decode().replace("\"","")
|
||||||
auth_token = AuthToken.objects.filter(key=token_actual)
|
return token_actual
|
||||||
if not auth_token.exists():
|
except Exception, e:
|
||||||
return False
|
logger.error("Exception validating user: " + str(e))
|
||||||
auth_token = auth_token[0]
|
return False
|
||||||
if not auth_token.expired:
|
return False
|
||||||
return auth_token.user
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
except Exception, e:
|
|
||||||
logger.error("Exception validating user: " + str(e))
|
|
||||||
return False
|
|
||||||
|
|
||||||
def recv_connect(self):
|
def recv_connect(self):
|
||||||
if not self.is_valid_connection:
|
socket_session = self.session.get('socket_session', None)
|
||||||
|
if socket_session and not socket_session.is_valid():
|
||||||
self.disconnect(silent=False)
|
self.disconnect(silent=False)
|
||||||
|
|
||||||
class TestNamespace(TowerBaseNamespace):
|
class TestNamespace(TowerBaseNamespace):
|
||||||
@@ -106,6 +195,14 @@ class ScheduleNamespace(TowerBaseNamespace):
|
|||||||
logger.info("Received client connect for schedule namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
logger.info("Received client connect for schedule namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
||||||
super(ScheduleNamespace, self).recv_connect()
|
super(ScheduleNamespace, self).recv_connect()
|
||||||
|
|
||||||
|
# Catch-all namespace.
|
||||||
|
# Deliver 'global' events over this namespace
|
||||||
|
class ControlNamespace(TowerBaseNamespace):
|
||||||
|
|
||||||
|
def recv_connect(self):
|
||||||
|
logger.warn("Received client connect for control namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
||||||
|
super(ControlNamespace, self).recv_connect()
|
||||||
|
|
||||||
class TowerSocket(object):
|
class TowerSocket(object):
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
@@ -115,7 +212,8 @@ class TowerSocket(object):
|
|||||||
'/socket.io/jobs': JobNamespace,
|
'/socket.io/jobs': JobNamespace,
|
||||||
'/socket.io/job_events': JobEventNamespace,
|
'/socket.io/job_events': JobEventNamespace,
|
||||||
'/socket.io/ad_hoc_command_events': AdHocCommandEventNamespace,
|
'/socket.io/ad_hoc_command_events': AdHocCommandEventNamespace,
|
||||||
'/socket.io/schedules': ScheduleNamespace})
|
'/socket.io/schedules': ScheduleNamespace,
|
||||||
|
'/socket.io/control': ControlNamespace})
|
||||||
else:
|
else:
|
||||||
logger.warn("Invalid connect path received: " + path)
|
logger.warn("Invalid connect path received: " + path)
|
||||||
start_response('404 Not Found', [])
|
start_response('404 Not Found', [])
|
||||||
@@ -130,13 +228,12 @@ def notification_handler(server):
|
|||||||
'name': message['event'],
|
'name': message['event'],
|
||||||
'type': 'event',
|
'type': 'event',
|
||||||
}
|
}
|
||||||
for session_id, socket in list(server.sockets.iteritems()):
|
|
||||||
if session_id in valid_sockets:
|
if 'token_key' in message:
|
||||||
try:
|
# Best practice not to send the token over the socket
|
||||||
socket.send_packet(packet)
|
SocketController.send_packet(packet, message.pop('token_key'))
|
||||||
except Exception, e:
|
else:
|
||||||
logger.error("Error sending client packet to %s: %s" % (str(session_id), str(packet)))
|
SocketController.broadcast_packet(packet)
|
||||||
logger.error("Error was: " + str(e))
|
|
||||||
|
|
||||||
class Command(NoArgsCommand):
|
class Command(NoArgsCommand):
|
||||||
'''
|
'''
|
||||||
@@ -164,6 +261,7 @@ class Command(NoArgsCommand):
|
|||||||
logger.info('Listening on port http://0.0.0.0:' + str(socketio_listen_port))
|
logger.info('Listening on port http://0.0.0.0:' + str(socketio_listen_port))
|
||||||
server = SocketIOServer(('0.0.0.0', socketio_listen_port), TowerSocket(), resource='socket.io')
|
server = SocketIOServer(('0.0.0.0', socketio_listen_port), TowerSocket(), resource='socket.io')
|
||||||
|
|
||||||
|
SocketController.set_server(server)
|
||||||
handler_thread = Thread(target=notification_handler, args=(server,))
|
handler_thread = Thread(target=notification_handler, args=(server,))
|
||||||
handler_thread.daemon = True
|
handler_thread.daemon = True
|
||||||
handler_thread.start()
|
handler_thread.start()
|
||||||
|
|||||||
522
awx/main/migrations/0072_v240_changes.py
Normal file
522
awx/main/migrations/0072_v240_changes.py
Normal file
@@ -0,0 +1,522 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from south.utils import datetime_utils as datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Adding field 'AuthToken.reason'
|
||||||
|
db.add_column(u'main_authtoken', 'reason',
|
||||||
|
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True),
|
||||||
|
keep_default=False)
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
# Deleting field 'AuthToken.reason'
|
||||||
|
db.delete_column(u'main_authtoken', 'reason')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
u'auth.group': {
|
||||||
|
'Meta': {'object_name': 'Group'},
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||||
|
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
u'auth.permission': {
|
||||||
|
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
|
||||||
|
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||||
|
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||||
|
},
|
||||||
|
u'auth.user': {
|
||||||
|
'Meta': {'object_name': 'User'},
|
||||||
|
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||||
|
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||||
|
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||||
|
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||||
|
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||||
|
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
|
||||||
|
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||||
|
},
|
||||||
|
u'contenttypes.contenttype': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||||
|
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||||
|
},
|
||||||
|
'main.activitystream': {
|
||||||
|
'Meta': {'object_name': 'ActivityStream'},
|
||||||
|
'actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_stream'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'ad_hoc_command': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.AdHocCommand']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'changes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||||
|
'credential': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Credential']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'custom_inventory_script': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.CustomInventoryScript']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'host': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Host']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Inventory']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'inventory_source': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventorySource']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'inventory_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventoryUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'job': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Job']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'job_template': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.JobTemplate']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'object1': ('django.db.models.fields.TextField', [], {}),
|
||||||
|
'object2': ('django.db.models.fields.TextField', [], {}),
|
||||||
|
'object_relationship_type': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||||
|
'operation': ('django.db.models.fields.CharField', [], {'max_length': '13'}),
|
||||||
|
'organization': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Organization']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'permission': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'project': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Project']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'project_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.ProjectUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'schedule': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Schedule']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'team': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Team']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||||
|
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'unified_job': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job+'", 'blank': 'True', 'to': "orm['main.UnifiedJob']"}),
|
||||||
|
'unified_job_template': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job_template+'", 'blank': 'True', 'to': "orm['main.UnifiedJobTemplate']"}),
|
||||||
|
'user': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.adhoccommand': {
|
||||||
|
'Meta': {'object_name': 'AdHocCommand', '_ormbases': ['main.UnifiedJob']},
|
||||||
|
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'credential': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
|
||||||
|
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
|
||||||
|
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ad_hoc_commands'", 'symmetrical': 'False', 'through': "orm['main.AdHocCommandEvent']", 'to': "orm['main.Host']"}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||||
|
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
|
||||||
|
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'module_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'module_name': ('django.db.models.fields.CharField', [], {'default': "'command'", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
|
||||||
|
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.adhoccommandevent': {
|
||||||
|
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('ad_hoc_command', 'host_name')]", 'object_name': 'AdHocCommandEvent'},
|
||||||
|
'ad_hoc_command': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_command_events'", 'to': "orm['main.AdHocCommand']"}),
|
||||||
|
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||||
|
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||||
|
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'host': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'ad_hoc_command_events'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
|
||||||
|
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'})
|
||||||
|
},
|
||||||
|
'main.authtoken': {
|
||||||
|
'Meta': {'object_name': 'AuthToken'},
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'expires': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||||
|
'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'reason': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'request_hash': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '40', 'blank': 'True'}),
|
||||||
|
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': u"orm['auth.User']"})
|
||||||
|
},
|
||||||
|
'main.credential': {
|
||||||
|
'Meta': {'ordering': "('kind', 'name')", 'unique_together': "[('user', 'team', 'kind', 'name')]", 'object_name': 'Credential'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'become_method': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
|
||||||
|
'become_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'become_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'cloud': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'host': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'kind': ('django.db.models.fields.CharField', [], {'default': "'ssh'", 'max_length': '32'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'project': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
|
||||||
|
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'team': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': "orm['main.Team']"}),
|
||||||
|
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': u"orm['auth.User']"}),
|
||||||
|
'username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'vault_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.custominventoryscript': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'CustomInventoryScript'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'custom_inventory_scripts'", 'to': "orm['main.Organization']"}),
|
||||||
|
'script': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.group': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
|
||||||
|
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
|
||||||
|
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
|
||||||
|
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.host': {
|
||||||
|
'Meta': {'ordering': "('inventory', 'name')", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'instance_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
|
||||||
|
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'hosts'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
|
||||||
|
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'hosts_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Job']"}),
|
||||||
|
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.instance': {
|
||||||
|
'Meta': {'object_name': 'Instance'},
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '250'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'})
|
||||||
|
},
|
||||||
|
'main.inventory': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'Inventory'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'inventory_sources_with_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||||
|
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"}),
|
||||||
|
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'total_inventory_sources': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.inventorysource': {
|
||||||
|
'Meta': {'object_name': 'InventorySource', '_ormbases': ['main.UnifiedJobTemplate']},
|
||||||
|
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventorysources'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'group': ('awx.main.fields.AutoOneToOneField', [], {'default': 'None', 'related_name': "'inventory_source'", 'unique': 'True', 'null': 'True', 'to': "orm['main.Group']"}),
|
||||||
|
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'inventory_sources'", 'null': 'True', 'to': "orm['main.Inventory']"}),
|
||||||
|
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
|
||||||
|
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
|
||||||
|
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
|
||||||
|
'update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||||
|
},
|
||||||
|
'main.inventoryupdate': {
|
||||||
|
'Meta': {'object_name': 'InventoryUpdate', '_ormbases': ['main.UnifiedJob']},
|
||||||
|
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventoryupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'inventory_source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventory_updates'", 'to': "orm['main.InventorySource']"}),
|
||||||
|
'license_error': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
|
||||||
|
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
|
||||||
|
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
|
||||||
|
},
|
||||||
|
'main.job': {
|
||||||
|
'Meta': {'ordering': "('id',)", 'object_name': 'Job', '_ormbases': ['main.UnifiedJob']},
|
||||||
|
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
|
||||||
|
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'jobs'", 'symmetrical': 'False', 'through': "orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||||
|
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
|
||||||
|
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
|
||||||
|
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.jobevent': {
|
||||||
|
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
|
||||||
|
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||||
|
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||||
|
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'host': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'job_events_as_primary_host'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
|
||||||
|
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||||
|
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'job_events'", 'symmetrical': 'False', 'to': "orm['main.Host']"}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.JobEvent']"}),
|
||||||
|
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||||
|
'role': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||||
|
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'})
|
||||||
|
},
|
||||||
|
'main.jobhostsummary': {
|
||||||
|
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host_name')]", 'object_name': 'JobHostSummary'},
|
||||||
|
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'host': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'job_host_summaries'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
|
||||||
|
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
|
||||||
|
},
|
||||||
|
'main.joborigin': {
|
||||||
|
'Meta': {'object_name': 'JobOrigin'},
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'instance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Instance']"}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'unified_job': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'job_origin'", 'unique': 'True', 'to': "orm['main.UnifiedJob']"})
|
||||||
|
},
|
||||||
|
'main.jobtemplate': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'object_name': 'JobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
|
||||||
|
'ask_variables_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
|
||||||
|
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||||
|
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
|
||||||
|
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'survey_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'survey_spec': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||||
|
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
|
||||||
|
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'main.organization': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'object_name': 'Organization'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||||
|
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': "orm['main.Project']"}),
|
||||||
|
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
|
||||||
|
},
|
||||||
|
'main.permission': {
|
||||||
|
'Meta': {'object_name': 'Permission'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Project']"}),
|
||||||
|
'run_ad_hoc_commands': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
|
||||||
|
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
|
||||||
|
},
|
||||||
|
'main.profile': {
|
||||||
|
'Meta': {'object_name': 'Profile'},
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'ldap_dn': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'user': ('awx.main.fields.AutoOneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': u"orm['auth.User']"})
|
||||||
|
},
|
||||||
|
'main.project': {
|
||||||
|
'Meta': {'ordering': "('id',)", 'object_name': 'Project', '_ormbases': ['main.UnifiedJobTemplate']},
|
||||||
|
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
|
||||||
|
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'scm_delete_on_next_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
|
||||||
|
'scm_update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
|
||||||
|
'scm_update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
|
||||||
|
},
|
||||||
|
'main.projectupdate': {
|
||||||
|
'Meta': {'object_name': 'ProjectUpdate', '_ormbases': ['main.UnifiedJob']},
|
||||||
|
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projectupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_updates'", 'to': "orm['main.Project']"}),
|
||||||
|
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
|
||||||
|
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
|
||||||
|
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
|
||||||
|
},
|
||||||
|
'main.schedule': {
|
||||||
|
'Meta': {'ordering': "['-next_run']", 'object_name': 'Schedule'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'dtend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||||
|
'dtstart': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||||
|
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'extra_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
|
||||||
|
'next_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||||
|
'rrule': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||||
|
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'schedules'", 'to': "orm['main.UnifiedJobTemplate']"})
|
||||||
|
},
|
||||||
|
'main.systemjob': {
|
||||||
|
'Meta': {'ordering': "('id',)", 'object_name': 'SystemJob', '_ormbases': ['main.UnifiedJob']},
|
||||||
|
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
|
||||||
|
'system_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.SystemJobTemplate']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
|
||||||
|
},
|
||||||
|
'main.systemjobtemplate': {
|
||||||
|
'Meta': {'object_name': 'SystemJobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
|
||||||
|
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
|
||||||
|
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
|
||||||
|
},
|
||||||
|
'main.team': {
|
||||||
|
'Meta': {'ordering': "('organization__name', 'name')", 'unique_together': "[('organization', 'name')]", 'object_name': 'Team'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
|
||||||
|
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': "orm['main.Project']"}),
|
||||||
|
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
|
||||||
|
},
|
||||||
|
'main.unifiedjob': {
|
||||||
|
'Meta': {'object_name': 'UnifiedJob'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'dependent_jobs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'dependent_jobs_rel_+'", 'to': "orm['main.UnifiedJob']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'elapsed': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '3'}),
|
||||||
|
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'finished': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'job_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
|
||||||
|
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
|
||||||
|
'job_explanation': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
|
||||||
|
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjob_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
|
||||||
|
'result_stdout_file': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'result_stdout_text': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.Schedule']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
|
||||||
|
'start_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'started': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||||
|
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
|
||||||
|
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjob_unified_jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJobTemplate']"})
|
||||||
|
},
|
||||||
|
'main.unifiedjobtemplate': {
|
||||||
|
'Meta': {'unique_together': "[('polymorphic_ctype', 'name')]", 'object_name': 'UnifiedJobTemplate'},
|
||||||
|
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'current_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_current_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
|
||||||
|
'has_schedules': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
|
||||||
|
'last_job_failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'last_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||||
|
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||||
|
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
|
||||||
|
'next_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
|
||||||
|
'next_schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_next_schedule+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Schedule']"}),
|
||||||
|
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
|
||||||
|
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjobtemplate_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
|
||||||
|
'status': ('django.db.models.fields.CharField', [], {'default': "'ok'", 'max_length': '32'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['main']
|
||||||
@@ -12,7 +12,8 @@ from django.conf import settings
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now as tz_now
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.fields import AutoOneToOneField
|
from awx.main.fields import AutoOneToOneField
|
||||||
@@ -164,13 +165,29 @@ class Profile(CreatedModifiedModel):
|
|||||||
default='',
|
default='',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
"""
|
||||||
|
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):
|
class AuthToken(BaseModel):
|
||||||
'''
|
'''
|
||||||
Custom authentication tokens per user with expiration and request-specific
|
Custom authentication tokens per user with expiration and request-specific
|
||||||
data.
|
data.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
REASON_CHOICES = [
|
||||||
|
('', _('Token not invalidated')),
|
||||||
|
('timeout_reached', _('Token is expired')),
|
||||||
|
('limit_reached', _('Maximum per-user sessions reached')),
|
||||||
|
# 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:
|
class Meta:
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
|
|
||||||
@@ -179,8 +196,21 @@ class AuthToken(BaseModel):
|
|||||||
on_delete=models.CASCADE)
|
on_delete=models.CASCADE)
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
modified = models.DateTimeField(auto_now=True)
|
modified = models.DateTimeField(auto_now=True)
|
||||||
expires = models.DateTimeField(default=now)
|
expires = models.DateTimeField(default=tz_now)
|
||||||
request_hash = models.CharField(max_length=40, blank=True, default='')
|
request_hash = 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 x[1]
|
||||||
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_request_hash(cls, request):
|
def get_request_hash(cls, request):
|
||||||
@@ -201,25 +231,65 @@ class AuthToken(BaseModel):
|
|||||||
self.key = self.generate_key()
|
self.key = self.generate_key()
|
||||||
return super(AuthToken, self).save(*args, **kwargs)
|
return super(AuthToken, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def refresh(self, save=True):
|
def refresh(self, now=None, save=True):
|
||||||
if not self.pk or not self.expired:
|
if not now:
|
||||||
self.expires = now() + datetime.timedelta(seconds=settings.AUTH_TOKEN_EXPIRATION)
|
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:
|
if save:
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def invalidate(self, save=True):
|
def invalidate(self, reason='timeout_reached', save=True):
|
||||||
if not self.expired:
|
if not AuthToken.reason_long(reason):
|
||||||
self.expires = now() - datetime.timedelta(seconds=1)
|
raise ValueError('Invalid reason specified')
|
||||||
if save:
|
self.reason = reason
|
||||||
self.save()
|
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):
|
def generate_key(self):
|
||||||
unique = uuid.uuid4()
|
unique = uuid.uuid4()
|
||||||
return hmac.new(unique.bytes, digestmod=hashlib.sha1).hexdigest()
|
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
|
@property
|
||||||
def expired(self):
|
def invalidated(self):
|
||||||
return bool(self.expires < now())
|
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')[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):
|
def __unicode__(self):
|
||||||
return self.key
|
return self.key
|
||||||
@@ -231,7 +301,7 @@ def user_mark_inactive(user, save=True):
|
|||||||
if user.is_active:
|
if user.is_active:
|
||||||
# Set timestamp to datetime.isoformat() but without the time zone
|
# Set timestamp to datetime.isoformat() but without the time zone
|
||||||
# offset to stay withint the 30 character username limit.
|
# offset to stay withint the 30 character username limit.
|
||||||
dtnow = now()
|
dtnow = tz_now()
|
||||||
deleted_ts = dtnow.strftime('%Y-%m-%dT%H:%M:%S.%f')
|
deleted_ts = dtnow.strftime('%Y-%m-%dT%H:%M:%S.%f')
|
||||||
user.username = '_d_%s' % deleted_ts
|
user.username = '_d_%s' % deleted_ts
|
||||||
user.is_active = False
|
user.is_active = False
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2015 Ansible, Inc.
|
# Copyright (c) 2015 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
from awx.main.tests.organizations import OrganizationsTest # noqa
|
from awx.main.tests.organizations import * # noqa
|
||||||
from awx.main.tests.users import * # noqa
|
from awx.main.tests.users import * # noqa
|
||||||
from awx.main.tests.inventory import * # noqa
|
from awx.main.tests.inventory import * # noqa
|
||||||
from awx.main.tests.projects import ProjectsTest, ProjectUpdatesTest # noqa
|
from awx.main.tests.projects import ProjectsTest, ProjectUpdatesTest # noqa
|
||||||
|
|||||||
@@ -1,10 +1,56 @@
|
|||||||
# Copyright (c) 2015 Ansible, Inc.
|
# Copyright (c) 2015 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
# Python
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
# Django
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils.timezone import now as tz_now
|
||||||
|
|
||||||
|
# AWX
|
||||||
from awx.main.models import * # noqa
|
from awx.main.models import * # noqa
|
||||||
from awx.main.tests.base import BaseTest
|
from awx.main.tests.base import BaseTest
|
||||||
|
|
||||||
|
__all__ = ['AuthTokenLimitUnitTest', 'OrganizationsTest']
|
||||||
|
|
||||||
|
class AuthTokenLimitUnitTest(BaseTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.now = tz_now()
|
||||||
|
# Times are relative to now
|
||||||
|
# (key, created on in seconds , expiration in seconds)
|
||||||
|
self.test_data = [
|
||||||
|
# a is implicitly expired
|
||||||
|
("a", -1000, -10),
|
||||||
|
# b's are invalid due to session limit of 3
|
||||||
|
("b", -100, 60),
|
||||||
|
("bb", -100, 60),
|
||||||
|
("c", -90, 70),
|
||||||
|
("d", -80, 80),
|
||||||
|
("e", -70, 90),
|
||||||
|
]
|
||||||
|
self.user = User.objects.create_superuser('admin', 'foo@bar.com', 'password')
|
||||||
|
for key, t_create, t_expire in self.test_data:
|
||||||
|
AuthToken.objects.create(
|
||||||
|
user=self.user,
|
||||||
|
key=key,
|
||||||
|
request_hash='this_is_a_hash',
|
||||||
|
created=self.now + timedelta(seconds=t_create),
|
||||||
|
expires=self.now + timedelta(seconds=t_expire),
|
||||||
|
)
|
||||||
|
super(AuthTokenLimitUnitTest, self).setUp()
|
||||||
|
|
||||||
|
@override_settings(AUTH_TOKEN_PER_USER=3)
|
||||||
|
def test_get_tokens_over_limit(self):
|
||||||
|
invalid_tokens = AuthToken.get_tokens_over_limit(self.user, now=self.now)
|
||||||
|
invalid_keys = [x.key for x in invalid_tokens]
|
||||||
|
self.assertEqual(len(invalid_keys), 2)
|
||||||
|
self.assertIn('b', invalid_keys)
|
||||||
|
self.assertIn('bb', invalid_keys)
|
||||||
|
|
||||||
class OrganizationsTest(BaseTest):
|
class OrganizationsTest(BaseTest):
|
||||||
|
|
||||||
def collection(self):
|
def collection(self):
|
||||||
|
|||||||
@@ -4,18 +4,20 @@
|
|||||||
# Python
|
# Python
|
||||||
import datetime
|
import datetime
|
||||||
import urllib
|
import urllib
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User, Group
|
from django.contrib.auth.models import User, Group
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import * # noqa
|
from awx.main.models import * # noqa
|
||||||
from awx.main.tests.base import BaseTest
|
from awx.main.tests.base import BaseTest
|
||||||
|
|
||||||
__all__ = ['AuthTokenTimeoutTest', 'AuthTokenProxyTest', 'UsersTest', 'LdapTest']
|
__all__ = ['AuthTokenTimeoutTest', 'AuthTokenLimitTest', 'AuthTokenProxyTest', 'UsersTest', 'LdapTest']
|
||||||
|
|
||||||
|
|
||||||
class AuthTokenTimeoutTest(BaseTest):
|
class AuthTokenTimeoutTest(BaseTest):
|
||||||
@@ -38,6 +40,41 @@ class AuthTokenTimeoutTest(BaseTest):
|
|||||||
self.assertIn('Auth-Token-Timeout', response)
|
self.assertIn('Auth-Token-Timeout', response)
|
||||||
self.assertEqual(response['Auth-Token-Timeout'], str(settings.AUTH_TOKEN_EXPIRATION))
|
self.assertEqual(response['Auth-Token-Timeout'], str(settings.AUTH_TOKEN_EXPIRATION))
|
||||||
|
|
||||||
|
class AuthTokenLimitTest(BaseTest):
|
||||||
|
def setUp(self):
|
||||||
|
super(AuthTokenLimitTest, self).setUp()
|
||||||
|
self.setup_users()
|
||||||
|
self.setup_instances()
|
||||||
|
|
||||||
|
@override_settings(AUTH_TOKEN_PER_USER=1)
|
||||||
|
@patch.object(awx.main.models.organization.AuthToken, 'get_request_hash')
|
||||||
|
def test_invalidate_first_session(self, mock_get_request_hash):
|
||||||
|
auth_token_url = reverse('api:auth_token_view')
|
||||||
|
user_me_url = reverse('api:user_me_list')
|
||||||
|
|
||||||
|
data = dict(zip(('username', 'password'), self.get_normal_credentials()))
|
||||||
|
|
||||||
|
mock_get_request_hash.return_value = "session_1"
|
||||||
|
response = self.post(auth_token_url, data, expect=200, auth=None)
|
||||||
|
auth_token1 = {
|
||||||
|
'token': response['token']
|
||||||
|
}
|
||||||
|
self.get(user_me_url, expect=200, auth=auth_token1)
|
||||||
|
|
||||||
|
mock_get_request_hash.return_value = "session_2"
|
||||||
|
response = self.post(auth_token_url, data, expect=200, auth=None)
|
||||||
|
auth_token2 = {
|
||||||
|
'token': response['token']
|
||||||
|
}
|
||||||
|
self.get(user_me_url, expect=200, auth=auth_token2)
|
||||||
|
|
||||||
|
# Ensure our get_request_hash mock is working
|
||||||
|
self.assertNotEqual(auth_token1['token'], auth_token2['token'])
|
||||||
|
|
||||||
|
mock_get_request_hash.return_value = "session_1"
|
||||||
|
response = self.get(user_me_url, expect=401, auth=auth_token1)
|
||||||
|
self.assertEqual(AuthToken.reason_long('limit_reached'), response['detail'])
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Ensure ips from the X-Forwarded-For get honored and used in auth tokens
|
Ensure ips from the X-Forwarded-For get honored and used in auth tokens
|
||||||
'''
|
'''
|
||||||
@@ -225,7 +262,7 @@ class UsersTest(BaseTest):
|
|||||||
remote_addr = '127.0.0.2'
|
remote_addr = '127.0.0.2'
|
||||||
response = self.get(user_me_url, expect=401, auth=auth_token,
|
response = self.get(user_me_url, expect=401, auth=auth_token,
|
||||||
remote_addr=remote_addr)
|
remote_addr=remote_addr)
|
||||||
self.assertEqual(response['detail'], 'Invalid token')
|
self.assertEqual(response['detail'], AuthToken.reason_long('invalid_token'))
|
||||||
|
|
||||||
# The WWW-Authenticate header should specify Token auth, since that
|
# The WWW-Authenticate header should specify Token auth, since that
|
||||||
# auth method was used in the request.
|
# auth method was used in the request.
|
||||||
|
|||||||
@@ -389,11 +389,13 @@ def get_system_task_capacity():
|
|||||||
return 50 + ((int(total_mem_value) / 1024) - 2) * 75
|
return 50 + ((int(total_mem_value) / 1024) - 2) * 75
|
||||||
|
|
||||||
|
|
||||||
def emit_websocket_notification(endpoint, event, payload):
|
def emit_websocket_notification(endpoint, event, payload, token_key=None):
|
||||||
from awx.main.socket import Socket
|
from awx.main.socket import Socket
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with Socket('websocket', 'w', nowait=True, logger=logger) as websocket:
|
with Socket('websocket', 'w', nowait=True, logger=logger) as websocket:
|
||||||
|
if token_key:
|
||||||
|
payload['token_key'] = token_key
|
||||||
payload['event'] = event
|
payload['event'] = event
|
||||||
payload['endpoint'] = endpoint
|
payload['endpoint'] = endpoint
|
||||||
websocket.publish(payload)
|
websocket.publish(payload)
|
||||||
|
|||||||
@@ -210,6 +210,10 @@ AUTH_LDAP_SERVER_URI = None
|
|||||||
# Seconds before auth tokens expire.
|
# Seconds before auth tokens expire.
|
||||||
AUTH_TOKEN_EXPIRATION = 1800
|
AUTH_TOKEN_EXPIRATION = 1800
|
||||||
|
|
||||||
|
# Maximum number of per-user valid, concurrent session.
|
||||||
|
# -1 is unlimited
|
||||||
|
AUTH_TOKEN_PER_USER = -1
|
||||||
|
|
||||||
# If set, serve only minified JS for UI.
|
# If set, serve only minified JS for UI.
|
||||||
USE_MINIFIED_JS = False
|
USE_MINIFIED_JS = False
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user