瀏覽代碼

[doc2] Add validations and tests

Jenny Kim 9 年之前
父節點
當前提交
f884935
共有 2 個文件被更改,包括 146 次插入49 次删除
  1. 50 36
      desktop/core/src/desktop/models.py
  2. 96 13
      desktop/core/src/desktop/tests_doc2.py

+ 50 - 36
desktop/core/src/desktop/models.py

@@ -19,6 +19,7 @@ import calendar
 import logging
 import json
 import os
+import re
 import uuid
 
 from itertools import chain
@@ -30,8 +31,6 @@ from django.contrib.staticfiles.storage import staticfiles_storage
 from django.core.urlresolvers import reverse
 from django.db import connection, models, transaction
 from django.db.models import Q
-from django.db.models.signals import pre_save, pre_delete, post_save
-from django.dispatch import receiver
 from django.template.defaultfilters import urlencode
 from django.utils.translation import ugettext as _, ugettext_lazy as _t
 
@@ -988,35 +987,43 @@ class Document2(models.Model):
     return history_doc
 
   def save(self, *args, **kwargs):
-    """
-    Override `save` to optionally mask out the query from being saved to the database. This is because if the database
-    contains sensitive information like personally identifiable information, that information could be leaked into the
-    Hue database and logfiles.
-    """
-    if global_redaction_engine.is_enabled() and self.type == 'notebook':
-      data_dict = self.data_dict
-      snippets = data_dict.get('snippets', [])
-      for snippet in snippets:
-        if snippet['type'] in ('hive', 'impala'):  # TODO: Pull SQL types from canonical lookup
-          redacted_statement_raw = global_redaction_engine.redact(snippet['statement_raw'])
-          if snippet['statement_raw'] != redacted_statement_raw:
-            snippet['statement_raw'] = redacted_statement_raw
-            snippet['statement'] = global_redaction_engine.redact(snippet['statement'])
-            snippet['is_redacted'] = True
-      self.data = json.dumps(data_dict)
-
-    # TODO: Validate name, shouldn't contain slashes
-    # TODO: Prevent documents with same name and location from being saved
-    # TODO: Prevent Home and Trash directories from being deleted
-    # TODO: Prevent creating home or trash directories in any location
-
-    # Save document to home directory if parent directory isn't specified
+    # Set document parent to home directory if parent directory isn't specified
     if not self.parent_directory and not self.is_home_directory and not self.is_trash_directory:
       home_dir = Document2.objects.get_home_directory(self.owner)
       self.parent_directory = home_dir
 
+    # Run validations
+    self.validate()
+
+    # Redact query if needed
+    self._redact_query()
+
     super(Document2, self).save(*args, **kwargs)
 
+  def validate(self):
+    # Validate document name
+    invalid_chars = re.compile(r"[<>/{}[\]~`]");
+    if invalid_chars.search(self.name):
+      raise FilesystemException(_('Document name contains an invalid character.'))
+
+    # Prevent documents with same name and parent from being created more than once
+    try:
+      document = Document2.objects.get(name=self.name, owner=self.owner, parent_directory=self.parent_directory)
+      if document.pk != self.pk:
+        raise FilesystemException(_('Document for owner %s at path %s already exists') % (self.owner, self.path))
+    except Document2.DoesNotExist:
+      pass  # no conflicts
+    except Document2.MultipleObjectsReturned:
+      document_ids = [doc.id for doc in Document2.objects.filter(name=self.name, owner=self.owner, parent_directory=self.parent_directory)]
+      raise FilesystemException(_('Found multiple documents for owner %s at path %s with IDs: [%s]') %
+                                (self.owner, self.path, ', '.join(document_ids)))
+
+    # Validate home and Trash directories are only created once per user and cannot be created or modified after
+    if self.name in ['', Document2.TRASH_DIR] and \
+       Document2.objects.filter(name=self.name, owner=self.owner, type='directory').exists():
+      raise FilesystemException(_('Cannot create or modify the home or .Trash directory.'))
+
+
   def move(self, directory, user):
     if not directory.is_directory:
       raise FilesystemException(_('Target with UUID %s is not a directory') % directory.uuid)
@@ -1077,6 +1084,24 @@ class Document2(models.Model):
       }
     }
 
+  def _redact_query(self):
+    """
+    Optionally mask out the query from being saved to the database. This is because if the database contains sensitive
+    information like personally identifiable information, that information could be leaked into the Hue database and
+    logfiles.
+    """
+    if global_redaction_engine.is_enabled() and self.type == 'notebook':
+      data_dict = self.data_dict
+      snippets = data_dict.get('snippets', [])
+      for snippet in snippets:
+        if snippet['type'] in ('hive', 'impala'):  # TODO: Pull SQL types from canonical lookup
+          redacted_statement_raw = global_redaction_engine.redact(snippet['statement_raw'])
+          if snippet['statement_raw'] != redacted_statement_raw:
+            snippet['statement_raw'] = redacted_statement_raw
+            snippet['statement'] = global_redaction_engine.redact(snippet['statement'])
+            snippet['is_redacted'] = True
+      self.data = json.dumps(data_dict)
+
 
 class DirectoryManager(Document2Manager):
 
@@ -1109,17 +1134,6 @@ class Directory(Document2):
   def save(self, *args, **kwargs):
     self.type = 'directory'
 
-    try:
-      directory = Directory.objects.get(name=self.name, owner=self.owner, type='directory')
-      if directory.pk != self.pk:
-        raise FilesystemException(_('Directory for owner %s at path already exists') % (self.owner, self.path))
-    except Directory.DoesNotExist:
-      pass  # no conflicts
-    except Directory.MultipleObjectsReturned:
-      directory_ids = [dir.id for dir in Directory.objects.filter(name=self.name, owner=self.owner, type='directory')]
-      raise FilesystemException(_('Found multiple directories for owner %s at path %s with IDs: [%s]') %
-                                (self.owner, self.path, ', '.join(directory_ids)))
-
     super(Directory, self).save(*args, **kwargs)
 
 

+ 96 - 13
desktop/core/src/desktop/tests_doc2.py

@@ -18,7 +18,7 @@
 
 import json
 
-from nose.tools import assert_equal, assert_true
+from nose.tools import assert_equal, assert_false, assert_true
 from django.contrib.auth.models import User
 
 from desktop.lib.django_test_util import make_logged_in_client
@@ -29,7 +29,6 @@ from beeswax.models import SavedQuery
 from beeswax.design import hql_query
 
 
-
 class TestDocument2(object):
 
   def setUp(self):
@@ -37,12 +36,17 @@ class TestDocument2(object):
     self.user = User.objects.get(username="doc2")
     grant_access("doc2", "doc2", "beeswax")
 
-    # Setup Home dir this way currently
+    # This creates the user directories for the new user
     response = self.client.get('/desktop/api2/docs/')
     data = json.loads(response.content)
-
     assert_equal('/', data['document']['path'], data)
 
+    self.home_dir = Document2.objects.get_home_directory(user=self.user)
+
+
+  def test_trash_directory(self):
+    assert_true(Directory.objects.filter(owner=self.user, name=Document2.TRASH_DIR, type='directory').exists())
+
 
   def test_document_create(self):
     sql = 'SELECT * FROM sample_07'
@@ -80,8 +84,7 @@ class TestDocument2(object):
 
 
   def test_directory_create(self):
-    home_dir = Document2.objects.get_home_directory(self.user)
-    response = self.client.post('/desktop/api2/doc/mkdir', {'parent_uuid': json.dumps(home_dir.uuid), 'name': json.dumps('test_mkdir')})
+    response = self.client.post('/desktop/api2/doc/mkdir', {'parent_uuid': json.dumps(self.home_dir.uuid), 'name': json.dumps('test_mkdir')})
     data = json.loads(response.content)
 
     assert_equal(0, data['status'], data)
@@ -91,12 +94,11 @@ class TestDocument2(object):
 
 
   def test_directory_move(self):
-    home_dir = Document2.objects.get_home_directory(self.user)
-    response = self.client.post('/desktop/api2/doc/mkdir', {'parent_uuid': json.dumps(home_dir.uuid), 'name': json.dumps('test_mv')})
+    response = self.client.post('/desktop/api2/doc/mkdir', {'parent_uuid': json.dumps(self.home_dir.uuid), 'name': json.dumps('test_mv')})
     data = json.loads(response.content)
     assert_equal(0, data['status'], data)
 
-    response = self.client.post('/desktop/api2/doc/mkdir', {'parent_uuid': json.dumps(home_dir.uuid), 'name': json.dumps('test_mv_dst')})
+    response = self.client.post('/desktop/api2/doc/mkdir', {'parent_uuid': json.dumps(self.home_dir.uuid), 'name': json.dumps('test_mv_dst')})
     data = json.loads(response.content)
     assert_equal(0, data['status'], data)
 
@@ -110,16 +112,15 @@ class TestDocument2(object):
     assert_equal(Directory.objects.get(name='test_mv', owner=self.user).path, '/test_mv_dst/test_mv')
 
 
-  def test_directory_documents(self):
-    home_dir = Directory.objects.get(owner=self.user, name='')
-
+  def test_directory_children(self):
+    # Creates 2 directories and 2 queries and saves to home directory
     dir1 = Directory.objects.create(name='test_dir1', owner=self.user)
     dir2 = Directory.objects.create(name='test_dir2', owner=self.user)
     query1 = Document2.objects.create(name='query1.sql', type='query-hive', owner=self.user, data={})
     query2 = Document2.objects.create(name='query2.sql', type='query-hive', owner=self.user, data={})
     children = [dir1, dir2, query1, query2]
 
-    home_dir.children.add(*children)
+    self.home_dir.children.add(*children)
 
     # Test that all children directories and documents are returned
     response = self.client.get('/desktop/api2/docs', {'path': '/'})
@@ -146,3 +147,85 @@ class TestDocument2(object):
     data = json.loads(response.content)
     assert_equal(5, data['count'])
     assert_equal(2, len(data['children']))
+
+
+  def test_document_trash(self):
+    # Create document under home and directory under home with child document
+    dir = Directory.objects.create(name='test_dir', owner=self.user, parent_directory=self.home_dir)
+    nested_query = Document2.objects.create(name='query1.sql', type='query-hive', owner=self.user, data={}, parent_directory=dir)
+    query = Document2.objects.create(name='query2.sql', type='query-hive', owner=self.user, data={}, parent_directory=self.home_dir)
+
+    # Test that .Trash is currently empty
+    response = self.client.get('/desktop/api2/docs', {'path': '/.Trash'})
+    data = json.loads(response.content)
+    assert_equal(0, data['count'])
+
+    # Delete document
+    response = self.client.post('/desktop/api2/doc/delete', {'uuid': json.dumps(query.uuid)})
+    data = json.loads(response.content)
+    assert_equal(0, data['status'])
+
+    response = self.client.get('/desktop/api2/docs', {'path': '/.Trash'})
+    data = json.loads(response.content)
+    assert_equal(1, data['count'])
+    assert_equal(data['children'][0]['uuid'], query.uuid)
+
+    # Delete directory
+    response = self.client.post('/desktop/api2/doc/delete', {'uuid': json.dumps(dir.uuid)})
+    data = json.loads(response.content)
+    assert_equal(0, data['status'], data)
+
+    response = self.client.get('/desktop/api2/docs', {'path': '/.Trash'})
+    data = json.loads(response.content)
+    assert_equal(2, data['count'])
+
+    # Verify that only doc in home is .Trash
+    response = self.client.get('/desktop/api2/docs', {'path': '/'})
+    data = json.loads(response.content)
+    assert_true('children' in data)
+    assert_equal(1, data['count'])
+    assert_equal(Document2.TRASH_DIR, data['children'][0]['name'])
+
+
+  def test_validations(self):
+    # Test invalid names
+    invalid_name = '/invalid'
+    response = self.client.post('/desktop/api2/doc/mkdir', {'parent_uuid': json.dumps(self.home_dir.uuid), 'name': json.dumps(invalid_name)})
+    data = json.loads(response.content)
+    assert_equal(-1, data['status'], data)
+    assert_true('invalid character' in data['message'])
+
+    # Test error on creating documents with same name and location
+    test_dir = Directory.objects.create(name='test_dir', owner=self.user, parent_directory=self.home_dir)
+    response = self.client.post('/desktop/api2/doc/mkdir', {'parent_uuid': json.dumps(self.home_dir.uuid), 'name': json.dumps('test_dir')})
+    data = json.loads(response.content)
+    assert_equal(-1, data['status'], data)
+    assert_true('/test_dir already exists' in data['message'])
+
+    # But can create same name in different location
+    response = self.client.post('/desktop/api2/doc/mkdir', {'parent_uuid': json.dumps(test_dir.uuid), 'name': json.dumps('test_dir')})
+    data = json.loads(response.content)
+    assert_equal(0, data['status'], data)
+
+    # Test that home and Trash directories cannot be recreated or modified
+    response = self.client.post('/desktop/api2/doc/mkdir', {'parent_uuid': json.dumps(test_dir.uuid), 'name': json.dumps(Document2.TRASH_DIR)})
+    data = json.loads(response.content)
+    assert_equal(-1, data['status'], data)
+    assert_equal('Cannot create or modify the home or .Trash directory.', data['message'])
+
+    response = self.client.post('/desktop/api2/doc/move', {
+        'source_doc_uuid': json.dumps(self.home_dir.uuid),
+        'destination_doc_uuid': json.dumps(test_dir.uuid)
+    })
+    data = json.loads(response.content)
+    assert_equal(-1, data['status'], data)
+    assert_equal('Cannot create or modify the home or .Trash directory.', data['message'])
+
+    trash_dir = Directory.objects.get(name=Document2.TRASH_DIR, owner=self.user)
+    response = self.client.post('/desktop/api2/doc/move', {
+        'source_doc_uuid': json.dumps(trash_dir.uuid),
+        'destination_doc_uuid': json.dumps(test_dir.uuid)
+    })
+    data = json.loads(response.content)
+    assert_equal(-1, data['status'], data)
+    assert_equal('Cannot create or modify the home or .Trash directory.', data['message'])