Exemple #1
0
 def delete(expected_ts):
   entity = self.get_entity_key(name).get()
   if not entity:
     if expected_ts:
       return False, {
         'http_code': 412,
         'text':
             '%s was deleted by someone else' %
             self.entity_kind_title.capitalize(),
       }
     else:
       # Unconditionally deleting it, and it's already gone -> success.
       return True, None
   if (expected_ts and
       utils.datetime_to_rfc2822(entity.modified_ts) != expected_ts):
     return False, {
       'http_code': 412,
       'text':
           '%s was modified by someone else' %
           self.entity_kind_title.capitalize(),
     }
   try:
     self.do_delete(entity)
   except EntityOperationError as exc:
     return False, {
       'http_code': 409,
       'text': exc.message,
       'details': exc.details,
     }
   return True, None
Exemple #2
0
 def delete(expected_ts):
   entity = self.get_entity_key(name).get()
   if not entity:
     if expected_ts:
       return False, {
         'http_code': 412,
         'text':
             '%s was deleted by someone else' %
             self.entity_kind_title.capitalize(),
       }
     else:
       # Unconditionally deleting it, and it's already gone -> success.
       return True, None
   if (expected_ts and
       utils.datetime_to_rfc2822(entity.modified_ts) != expected_ts):
     return False, {
       'http_code': 412,
       'text':
           '%s was modified by someone else' %
           self.entity_kind_title.capitalize(),
     }
   entity.record_deletion(
       modified_by=api.get_current_identity(),
       modified_ts=utils.utcnow(),
       comment='REST API')
   try:
     self.do_delete(entity)
   except EntityOperationError as exc:
     return False, {
       'http_code': 409,
       'text': exc.message,
       'details': exc.details,
     }
   model.replicate_auth_db()
   return True, None
Exemple #3
0
 def update(params, expected_ts):
   entity = self.get_entity_key(name).get()
   if not entity:
     return None, {
       'http_code': 404,
       'text': 'No such %s' % self.entity_kind_title,
     }
   if (expected_ts and
       utils.datetime_to_rfc2822(entity.modified_ts) != expected_ts):
     return None, {
       'http_code': 412,
       'text':
           '%s was modified by someone else' %
           self.entity_kind_title.capitalize(),
     }
   entity.record_revision(
       modified_by=api.get_current_identity(),
       modified_ts=utils.utcnow(),
       comment='REST API')
   try:
     self.do_update(entity, params)
   except EntityOperationError as exc:
     return None, {
       'http_code': 409,
       'text': exc.message,
       'details': exc.details,
     }
   except ValueError as exc:
     return False, {
       'http_code': 400,
       'text': str(exc),
     }
   model.replicate_auth_db()
   return entity, None
Exemple #4
0
 def update(params, modified_by, expected_ts):
   entity = self.get_entity_key(name).get()
   if not entity:
     return None, {
       'http_code': 404,
       'text': 'No such %s' % self.entity_kind_title,
     }
   if (expected_ts and
       utils.datetime_to_rfc2822(entity.modified_ts) != expected_ts):
     return None, {
       'http_code': 412,
       'text':
           '%s was modified by someone else' %
           self.entity_kind_title.capitalize(),
     }
   try:
     self.do_update(entity, params, modified_by)
   except EntityOperationError as exc:
     return None, {
       'http_code': 409,
       'text': exc.message,
       'details': exc.details,
     }
   except ValueError as exc:
     return False, {
       'http_code': 400,
       'text': str(exc),
     }
   return entity, None
Exemple #5
0
  def post(self, name):
    """Creates a new entity, ensuring it's indeed new (no overwrites)."""
    self.check_preconditions()
    try:
      body = self.parse_body()
      name_in_body = body.pop('name', None)
      if not name_in_body or name_in_body != name:
        raise ValueError('Missing or mismatching name in request body')
      if not self.is_entity_writable(name):
        raise ValueError('This %s is not writable' % self.entity_kind_title)
      entity = self.entity_kind.from_serializable_dict(
          serializable_dict=body,
          key=self.get_entity_key(name),
          created_ts=utils.utcnow(),
          created_by=api.get_current_identity())
    except (TypeError, ValueError) as e:
      self.abort_with_error(400, text=str(e))

    @ndb.transactional
    def create(entity):
      if entity.key.get():
        return False, {
          'http_code': 409,
          'text': 'Such %s already exists' % self.entity_kind_title,
        }
      entity.record_revision(
          modified_by=api.get_current_identity(),
          modified_ts=utils.utcnow(),
          comment='REST API')
      try:
        self.do_create(entity)
      except EntityOperationError as exc:
        return False, {
          'http_code': 409,
          'text': exc.message,
          'details': exc.details,
        }
      except ValueError as exc:
        return False, {
          'http_code': 400,
          'text': str(exc),
        }
      model.replicate_auth_db()
      return True, None

    success, error_details = create(entity)
    if not success:
      self.abort_with_error(**error_details)
    self.send_response(
        response={'ok': True},
        http_code=201,
        headers={
          'Last-Modified': utils.datetime_to_rfc2822(entity.modified_ts),
          'Location':
              '%s%s' % (self.entity_url_prefix, urllib.quote(entity.key.id())),
        }
    )
Exemple #6
0
 def get(self, name):
   """Fetches entity give its name."""
   self.check_preconditions()
   obj = self.get_entity_key(name).get()
   if not obj:
     self.abort_with_error(404, text='No such %s' % self.entity_kind_title)
   self.send_response(
       response={self.entity_kind_name: self.entity_to_dict(obj)},
       headers={'Last-Modified': utils.datetime_to_rfc2822(obj.modified_ts)})
Exemple #7
0
 def get(self, name):
   """Fetches entity give its name."""
   self.check_preconditions()
   obj = self.do_get(name, self.request)
   if not obj:
     self.abort_with_error(404, text='No such %s' % self.entity_kind_title)
   self.send_response(
       response={self.entity_kind_name: self.entity_to_dict(obj)},
       headers={'Last-Modified': utils.datetime_to_rfc2822(obj.modified_ts)})
Exemple #8
0
  def put(self, name):
    """Updates an existing entity."""
    try:
      body = self.parse_body()
      name_in_body = body.pop('name', None)
      if not name_in_body or name_in_body != name:
        raise ValueError('Missing or mismatching name in request body')
      if not self.is_entity_writable(name):
        raise ValueError('This %s is not writable' % self.entity_kind_title)
      entity_params = self.entity_kind.convert_serializable_dict(body)
    except (TypeError, ValueError) as e:
      self.abort_with_error(400, text=str(e))

    @ndb.transactional
    def update(params, modified_by, expected_ts):
      entity = self.get_entity_key(name).get()
      if not entity:
        return None, {
          'http_code': 404,
          'text': 'No such %s' % self.entity_kind_title,
        }
      if (expected_ts and
          utils.datetime_to_rfc2822(entity.modified_ts) != expected_ts):
        return None, {
          'http_code': 412,
          'text':
              '%s was modified by someone else' %
              self.entity_kind_title.capitalize(),
        }
      try:
        self.do_update(entity, params, modified_by)
      except EntityOperationError as exc:
        return None, {
          'http_code': 409,
          'text': exc.message,
          'details': exc.details,
        }
      except ValueError as exc:
        return False, {
          'http_code': 400,
          'text': str(exc),
        }
      return entity, None

    entity, error_details = update(
        entity_params,
        api.get_current_identity(),
        self.request.headers.get('If-Unmodified-Since'))
    if not entity:
      self.abort_with_error(**error_details)
    self.send_response(
        response={'ok': True},
        http_code=200,
        headers={
          'Last-Modified': utils.datetime_to_rfc2822(entity.modified_ts),
        }
    )
Exemple #9
0
 def get(self, name):
   """Fetches entity give its name."""
   obj = self.get_entity_key(name).get()
   if not obj:
     self.abort_with_error(404, text='No such %s' % self.entity_kind_title)
   self.send_response(
       response={
         self.entity_kind_name: obj.to_serializable_dict(with_id_as='name'),
       },
       headers={'Last-Modified': utils.datetime_to_rfc2822(obj.modified_ts)})
Exemple #10
0
  def post(self, name):
    """Creates a new entity, ensuring it's indeed new (no overwrites)."""
    try:
      body = self.parse_body()
      name_in_body = body.pop('name', None)
      if not name_in_body or name_in_body != name:
        raise ValueError('Missing or mismatching name in request body')
      if not self.is_entity_writable(name):
        raise ValueError('This %s is not writable' % self.entity_kind_title)
      entity = self.entity_kind.from_serializable_dict(
          serializable_dict=body,
          key=self.get_entity_key(name),
          created_by=api.get_current_identity(),
          modified_by=api.get_current_identity())
    except (TypeError, ValueError) as e:
      self.abort_with_error(400, text=str(e))

    @ndb.transactional
    def create(entity):
      if entity.key.get():
        return False, {
          'http_code': 409,
          'text': 'Such %s already exists' % self.entity_kind_title,
        }
      try:
        self.do_create(entity)
      except EntityOperationError as exc:
        return False, {
          'http_code': 409,
          'text': exc.message,
          'details': exc.details,
        }
      except ValueError as exc:
        return False, {
          'http_code': 400,
          'text': str(exc),
        }
      return True, None

    success, error_details = create(entity)
    if not success:
      self.abort_with_error(**error_details)
    self.send_response(
        response={'ok': True},
        http_code=201,
        headers={
          'Last-Modified': utils.datetime_to_rfc2822(entity.modified_ts),
          'Location':
              '%s%s' % (self.entity_url_prefix, urllib.quote(entity.key.id())),
        }
    )
Exemple #11
0
 def delete(expected_ts):
     entity = self.get_entity_key(name).get()
     if not entity:
         if expected_ts:
             return None, {
                 'http_code':
                 412,
                 'text':
                 '%s was deleted by someone else' %
                 self.entity_kind_title.capitalize(),
             }
         else:
             # Unconditionally deleting it, and it's already gone -> success.
             return None, None
     if (expected_ts and utils.datetime_to_rfc2822(entity.modified_ts)
             != expected_ts):
         return None, {
             'http_code':
             412,
             'text':
             '%s was modified by someone else' %
             self.entity_kind_title.capitalize(),
         }
     if not self.can_delete(entity):
         # Raising from inside a transaction produces ugly logs. Just return the
         # exception to be raised outside.
         ident = api.get_current_identity()
         exc = api.AuthorizationError(
             '"%s" has no permission to delete %s "%s"' %
             (ident.to_bytes(), self.entity_kind_title, name))
         return exc, None
     entity.record_deletion(modified_by=api.get_current_identity(),
                            modified_ts=utils.utcnow(),
                            comment='REST API')
     try:
         self.do_delete(entity)
     except EntityOperationError as exc:
         return None, {
             'http_code': 409,
             'text': exc.message,
             'details': exc.details,
         }
     model.replicate_auth_db()
     return None, None
Exemple #12
0
def generate_changes(auth_db_rev):
    """Generates change log for entities modified in given revision.

  Starts a chain of tasks to process all previous revisions too (if not yet
  processed).
  """
    # Already done?
    rev = change_log_revision_key(auth_db_rev).get()
    if rev:
        logging.info('Rev %d was already processed at %s by app ver %s',
                     auth_db_rev, utils.datetime_to_rfc2822(rev.when),
                     rev.app_version)
        return

    # Use kindless query to grab _all_ changed entities regardless of their kind.
    # Do keys only query to workaround ndb's inability to work with dynamically
    # generated classes (e.g. *History).
    q = ndb.Query(ancestor=model.historical_revision_key(auth_db_rev))
    changes = []
    for change_list in q.map(diff_entity_by_key, keys_only=True):
        changes.extend(change_list or [])
    logging.info('Changes found: %d', len(changes))

    # Commit changes, start processing previous version if not yet done. Need to
    # write AuthDBLogRev marker even if there were no significant changes.
    @ndb.transactional
    def commit():
        if change_log_revision_key(auth_db_rev).get():
            logging.warning('Rev %d was already processed concurrently',
                            auth_db_rev)
            return
        rev = AuthDBLogRev(key=change_log_revision_key(auth_db_rev),
                           when=utils.utcnow(),
                           app_version=utils.get_app_version())
        ndb.put_multi(changes + [rev])
        # Enqueue a task to process previous version if not yet done.
        if auth_db_rev > 1:
            prev_rev = auth_db_rev - 1
            if not change_log_revision_key(prev_rev).get():
                logging.info('Enqueuing task to process rev %d', prev_rev)
                enqueue_process_change_task(prev_rev)

    commit()
Exemple #13
0
 def delete(expected_ts):
   entity = self.get_entity_key(name).get()
   if not entity:
     if expected_ts:
       return None, {
         'http_code': 412,
         'text':
             '%s was deleted by someone else' %
             self.entity_kind_title.capitalize(),
       }
     else:
       # Unconditionally deleting it, and it's already gone -> success.
       return None, None
   if (expected_ts and
       utils.datetime_to_rfc2822(entity.modified_ts) != expected_ts):
     return None, {
       'http_code': 412,
       'text':
           '%s was modified by someone else' %
           self.entity_kind_title.capitalize(),
     }
   if not self.can_delete(entity):
     # Raising from inside a transaction produces ugly logs. Just return the
     # exception to be raised outside.
     ident = api.get_current_identity()
     exc = api.AuthorizationError(
         '"%s" has no permission to delete %s "%s"' %
         (ident.to_bytes(), self.entity_kind_title, name))
     return exc, None
   entity.record_deletion(
       modified_by=api.get_current_identity(),
       modified_ts=utils.utcnow(),
       comment='REST API')
   try:
     self.do_delete(entity)
   except EntityOperationError as exc:
     return None, {
       'http_code': 409,
       'text': exc.message,
       'details': exc.details,
     }
   model.replicate_auth_db()
   return None, None
Exemple #14
0
 def update(params, expected_ts):
   entity = self.get_entity_key(name).get()
   if not entity:
     return None, None, {
       'http_code': 404,
       'text': 'No such %s' % self.entity_kind_title,
     }
   if (expected_ts and
       utils.datetime_to_rfc2822(entity.modified_ts) != expected_ts):
     return None, None, {
       'http_code': 412,
       'text':
           '%s was modified by someone else' %
           self.entity_kind_title.capitalize(),
     }
   if not self.can_update(entity):
     # Raising from inside a transaction produces ugly logs. Just return the
     # exception to be raised outside.
     ident = api.get_current_identity()
     exc = api.AuthorizationError(
         '"%s" has no permission to update %s "%s"' %
         (ident.to_bytes(), self.entity_kind_title, name))
     return None, exc, None
   entity.record_revision(
       modified_by=api.get_current_identity(),
       modified_ts=utils.utcnow(),
       comment='REST API')
   try:
     self.do_update(entity, params)
   except EntityOperationError as exc:
     return None, None, {
       'http_code': 409,
       'text': exc.message,
       'details': exc.details,
     }
   except ValueError as exc:
     return None, None, {
       'http_code': 400,
       'text': str(exc),
     }
   model.replicate_auth_db()
   return entity, None, None
Exemple #15
0
def generate_changes(auth_db_rev):
  """Generates change log for entities modified in given revision.

  Starts a chain of tasks to process all previous revisions too (if not yet
  processed).
  """
  # Already done?
  rev = change_log_revision_key(auth_db_rev).get()
  if rev:
    logging.info(
        'Rev %d was already processed at %s by app ver %s',
        auth_db_rev, utils.datetime_to_rfc2822(rev.when), rev.app_version)
    return

  # Use kindless query to grab _all_ changed entities regardless of their kind.
  # Do keys only query to workaround ndb's inability to work with dynamically
  # generated classes (e.g. *History).
  q = ndb.Query(ancestor=model.historical_revision_key(auth_db_rev))
  changes = []
  for change_list in q.map(diff_entity_by_key, keys_only=True):
    changes.extend(change_list or [])
  logging.info('Changes found: %d', len(changes))

  # Commit changes, start processing previous version if not yet done. Need to
  # write AuthDBLogRev marker even if there were no significant changes.
  @ndb.transactional
  def commit():
    if change_log_revision_key(auth_db_rev).get():
      logging.warning('Rev %d was already processed concurrently', auth_db_rev)
      return
    rev = AuthDBLogRev(
        key=change_log_revision_key(auth_db_rev),
        when=utils.utcnow(),
        app_version=utils.get_app_version())
    ndb.put_multi(changes + [rev])
    # Enqueue a task to process previous version if not yet done.
    if auth_db_rev > 1:
      prev_rev = auth_db_rev - 1
      if not change_log_revision_key(prev_rev).get():
        logging.info('Enqueuing task to process rev %d', prev_rev)
        enqueue_process_change_task(prev_rev)
  commit()
Exemple #16
0
 def update(params, expected_ts):
     entity = self.get_entity_key(name).get()
     if not entity:
         return None, None, {
             'http_code': 404,
             'text': 'No such %s' % self.entity_kind_title,
         }
     if (expected_ts and utils.datetime_to_rfc2822(entity.modified_ts)
             != expected_ts):
         return None, None, {
             'http_code':
             412,
             'text':
             '%s was modified by someone else' %
             self.entity_kind_title.capitalize(),
         }
     if not self.can_update(entity):
         # Raising from inside a transaction produces ugly logs. Just return the
         # exception to be raised outside.
         ident = api.get_current_identity()
         exc = api.AuthorizationError(
             '"%s" has no permission to update %s "%s"' %
             (ident.to_bytes(), self.entity_kind_title, name))
         return None, exc, None
     entity.record_revision(modified_by=api.get_current_identity(),
                            modified_ts=utils.utcnow(),
                            comment='REST API')
     try:
         self.do_update(entity, params)
     except EntityOperationError as exc:
         return None, None, {
             'http_code': 409,
             'text': exc.message,
             'details': exc.details,
         }
     except ValueError as exc:
         return None, None, {
             'http_code': 400,
             'text': str(exc),
         }
     model.replicate_auth_db()
     return entity, None, None
Exemple #17
0
 def test_datetime_to_rfc2822(self):
     self.assertEqual(
         'Mon, 02 Jan 2012 03:04:05 -0000',
         utils.datetime_to_rfc2822(datetime.datetime(2012, 1, 2, 3, 4, 5)))
Exemple #18
0
 def test_datetime_to_rfc2822(self):
   self.assertEqual(
     'Mon, 02 Jan 2012 03:04:05 -0000',
     utils.datetime_to_rfc2822(datetime.datetime(2012, 1, 2, 3, 4, 5)))
Exemple #19
0
  def put(self, name):
    """Updates an existing entity."""
    self.check_preconditions()
    try:
      body = self.parse_body()
      name_in_body = body.pop('name', None)
      if not name_in_body or name_in_body != name:
        raise ValueError('Missing or mismatching name in request body')
      if not self.is_entity_writable(name):
        raise ValueError('This %s is not writable' % self.entity_kind_title)
      entity_params = self.entity_kind.convert_serializable_dict(body)
    except (TypeError, ValueError) as e:
      self.abort_with_error(400, text=str(e))

    @ndb.transactional
    def update(params, expected_ts):
      entity = self.get_entity_key(name).get()
      if not entity:
        return None, None, {
          'http_code': 404,
          'text': 'No such %s' % self.entity_kind_title,
        }
      if (expected_ts and
          utils.datetime_to_rfc2822(entity.modified_ts) != expected_ts):
        return None, None, {
          'http_code': 412,
          'text':
              '%s was modified by someone else' %
              self.entity_kind_title.capitalize(),
        }
      if not self.can_update(entity):
        # Raising from inside a transaction produces ugly logs. Just return the
        # exception to be raised outside.
        ident = api.get_current_identity()
        exc = api.AuthorizationError(
            '"%s" has no permission to update %s "%s"' %
            (ident.to_bytes(), self.entity_kind_title, name))
        return None, exc, None
      entity.record_revision(
          modified_by=api.get_current_identity(),
          modified_ts=utils.utcnow(),
          comment='REST API')
      try:
        self.do_update(entity, params)
      except EntityOperationError as exc:
        return None, None, {
          'http_code': 409,
          'text': exc.message,
          'details': exc.details,
        }
      except ValueError as exc:
        return None, None, {
          'http_code': 400,
          'text': str(exc),
        }
      model.replicate_auth_db()
      return entity, None, None

    entity, exc, error_details = update(
        entity_params, self.request.headers.get('If-Unmodified-Since'))
    if exc:
      raise exc  # pylint: disable=raising-bad-type
    if not entity:
      self.abort_with_error(**error_details)
    self.send_response(
        response={'ok': True},
        http_code=200,
        headers={
          'Last-Modified': utils.datetime_to_rfc2822(entity.modified_ts),
        }
    )
Exemple #20
0
    def put(self, name):
        """Updates an existing entity."""
        self.check_preconditions()
        try:
            body = self.parse_body()
            name_in_body = body.pop('name', None)
            if not name_in_body or name_in_body != name:
                raise ValueError('Missing or mismatching name in request body')
            if not self.is_entity_writable(name):
                raise ValueError('This %s is not writable' %
                                 self.entity_kind_title)
            entity_params = self.entity_kind.convert_serializable_dict(body)
        except (TypeError, ValueError) as e:
            self.abort_with_error(400, text=str(e))

        @ndb.transactional
        def update(params, expected_ts):
            entity = self.get_entity_key(name).get()
            if not entity:
                return None, None, {
                    'http_code': 404,
                    'text': 'No such %s' % self.entity_kind_title,
                }
            if (expected_ts and utils.datetime_to_rfc2822(entity.modified_ts)
                    != expected_ts):
                return None, None, {
                    'http_code':
                    412,
                    'text':
                    '%s was modified by someone else' %
                    self.entity_kind_title.capitalize(),
                }
            if not self.can_update(entity):
                # Raising from inside a transaction produces ugly logs. Just return the
                # exception to be raised outside.
                ident = api.get_current_identity()
                exc = api.AuthorizationError(
                    '"%s" has no permission to update %s "%s"' %
                    (ident.to_bytes(), self.entity_kind_title, name))
                return None, exc, None
            entity.record_revision(modified_by=api.get_current_identity(),
                                   modified_ts=utils.utcnow(),
                                   comment='REST API')
            try:
                self.do_update(entity, params)
            except EntityOperationError as exc:
                return None, None, {
                    'http_code': 409,
                    'text': exc.message,
                    'details': exc.details,
                }
            except ValueError as exc:
                return None, None, {
                    'http_code': 400,
                    'text': str(exc),
                }
            model.replicate_auth_db()
            return entity, None, None

        entity, exc, error_details = update(
            entity_params, self.request.headers.get('If-Unmodified-Since'))
        if exc:
            raise exc  # pylint: disable=raising-bad-type
        if not entity:
            self.abort_with_error(**error_details)
        self.send_response(response={'ok': True},
                           http_code=200,
                           headers={
                               'Last-Modified':
                               utils.datetime_to_rfc2822(entity.modified_ts),
                           })