Ejemplo n.º 1
0
  def get(self):
    """Process an Action task with the correct Action class."""
    audit_hours = config_model.Config.get('shelf_audit_interval')
    earliest_time = (
        datetime.datetime.utcnow() - datetime.timedelta(hours=audit_hours))
    # pylint: disable=g-explicit-bool-comparison, g-equals-none
    global_setting_query = shelf_model.Shelf.query(
        shelf_model.Shelf.audit_notification_enabled == True,  # pylint: disable=singleton-comparison
        shelf_model.Shelf.audit_requested == False,  # pylint: disable=singleton-comparison
        shelf_model.Shelf.last_audit_time < earliest_time,
        shelf_model.Shelf.audit_interval_override == None)  # pylint: disable=singleton-comparison

    override_query = shelf_model.Shelf.query(
        shelf_model.Shelf.audit_interval_override != None,
        shelf_model.Shelf.audit_notification_enabled == True,  # pylint: disable=singleton-comparison
        shelf_model.Shelf.audit_requested == False)  # pylint: disable=singleton-comparison
    # pylint: enable=g-explicit-bool-comparison, g-equals-none

    override_shelves = []
    for shelf in override_query.fetch():
      override_earliest_time = (
          datetime.datetime.utcnow() - datetime.timedelta(
              hours=shelf.audit_interval_override))
      if (
          shelf.last_audit_time and
          shelf.last_audit_time < override_earliest_time):
        override_shelves.append(shelf)

    for shelf in override_shelves + global_setting_query.fetch():
      events.raise_event(event_name='shelf_needs_audit', shelf=shelf)
Ejemplo n.º 2
0
  def loan_assign(self, user_email):
    """Assigns a device to a user.

    Args:
      user_email: str, email address of the user to whom the device should be
          assigned.

    Returns:
      The key of the datastore record.

    Raises:
      AssignmentError: if the device is not enrolled.
    """
    if not self.enrolled:
      raise AssignmentError('Cannot assign an unenrolled device.')

    if self.assigned_user and self.assigned_user != user_email:
      self._loan_return(user_email)

    self.assigned_user = user_email
    self.assignment_date = datetime.datetime.utcnow()
    self.mark_pending_return_date = None
    self.shelf = None
    self.due_date = self.calculate_return_dates().default
    self.move_to_default_ou(user_email=user_email)
    self.put()
    self.stream_to_bq(user_email, 'Beginning new loan.')
    events.raise_event('device_loan_assign', device=self)
    return self.key
Ejemplo n.º 3
0
 def _remind_for_devices(self):
     """Find devices marked as being in a remindable state and raise event."""
     for device in device_model.Device.query(
             device_model.Device.next_reminder.time <=
             datetime.datetime.utcnow()).fetch():
         logging.info(_DEVICE_REMINDING_NOW_MSG, device.identifier,
                      device.next_reminder.level)
         events.raise_event(event_name=event_models.ReminderEvent.make_name(
             device.next_reminder.level),
                            device=device)
Ejemplo n.º 4
0
  def disable(self, user_email):
    """Marks a shelf as disabled.

    Args:
      user_email: str, email of the user disabling the shelf.
    """
    self.enabled = False
    logging.info(_DISABLE_MSG, self.name)
    self.put()
    events.raise_event('shelf_disable', shelf=self)
    self.stream_to_bq(user_email, _DISABLE_MSG % self.name)
Ejemplo n.º 5
0
 def get(self):
     """Process an Action task with the correct Action class."""
     custom_events = event_models.CustomEvent.get_all_enabled()
     for custom_event in custom_events:
         for entity in custom_event.get_matching_entities():
             device = (entity
                       if custom_event.model.lower() == 'device' else None)
             shelf = (entity
                      if custom_event.model.lower() == 'shelf' else None)
             events.raise_event(event_name=custom_event.name,
                                device=device,
                                shelf=shelf)
Ejemplo n.º 6
0
  def audit(self, user_email):
    """Marks a shelf audited.

    Args:
      user_email: str, email of the user auditing the shelf.
    """
    self.last_audit_time = datetime.datetime.utcnow()
    self.last_audit_by = user_email
    self.audit_requested = False
    logging.info(_AUDIT_MSG, self.name)
    self.put()
    events.raise_event('shelf_audited', shelf=self)
    self.stream_to_bq(user_email, _AUDIT_MSG % self.name)
 def _remind_for_devices(self):
     """Find devices marked as being in a remindable state and raise event."""
     for device in device_model.Device.query(
             device_model.Device.next_reminder.time <=
             datetime.datetime.utcnow()).fetch():
         logging.info(_DEVICE_REMINDING_NOW_MSG, device.identifier,
                      device.next_reminder.level)
         try:
             events.raise_event(
                 event_name=event_models.ReminderEvent.make_name(
                     device.next_reminder.level),
                 device=device)
         except events.EventActionsError as err:
             # We log the error so that a single device does not disrupt all other
             # devices that need reminders set.
             logging.error(_EVENT_ACTION_ERROR_MSG, err)
Ejemplo n.º 8
0
  def _loan_return(self, user_email):
    """Returns a device in a loan.

    Args:
      user_email: str, user_email of the user initiating the return.

    Returns:
      The key of the datastore record.
    """
    event_action = 'device_loan_return'
    try:
      self = events.raise_event(event_action, device=self)
    except events.EventActionsError as err:
      # For any action that is implemented for device_loan_return that is
      # required for the rest of the logic, an error should be raised. If all
      # actions are not required, eg sending a notification email only, the
      # error should only be logged.
      logging.error(_EVENT_ACTION_ERROR_MSG, event_action, err)
    if self.lost:
      self.lost = False
    if self.locked:
      self.unlock(user_email)
    self.assigned_user = None
    self.assignment_date = None
    self.due_date = None
    self.mark_pending_return_date = None
    self.move_to_default_ou(user_email=user_email)
    self.last_reminder = None
    self.next_reminder = None
    self.put()
    self.stream_to_bq(
        user_email, 'Marking device %s as returned.' % self.identifier)
    return self.key
Ejemplo n.º 9
0
    def get(self):
        """Process an Action task with the correct Action class."""
        if config_model.Config.get('shelf_audit'):
            audit_hours = config_model.Config.get('shelf_audit_interval')
            earliest_time = (datetime.datetime.utcnow() -
                             datetime.timedelta(hours=audit_hours))
            # pylint: disable=g-explicit-bool-comparison, g-equals-none
            global_setting_query = shelf_model.Shelf.query(
                shelf_model.Shelf.audit_notification_enabled == True,  # pylint: disable=singleton-comparison
                shelf_model.Shelf.audit_requested == False,  # pylint: disable=singleton-comparison
                shelf_model.Shelf.last_audit_time != None,
                shelf_model.Shelf.last_audit_time < earliest_time,
                shelf_model.Shelf.audit_interval_override == None)  # pylint: disable=singleton-comparison

            override_query = shelf_model.Shelf.query(
                shelf_model.Shelf.audit_interval_override != None,
                shelf_model.Shelf.audit_notification_enabled == True,  # pylint: disable=singleton-comparison
                shelf_model.Shelf.audit_requested == False)  # pylint: disable=singleton-comparison
            # pylint: enable=g-explicit-bool-comparison, g-equals-none

            override_shelves = []
            for shelf in override_query.fetch():
                override_earliest_time = (
                    datetime.datetime.utcnow() -
                    datetime.timedelta(hours=shelf.audit_interval_override))
                if (shelf.last_audit_time
                        and shelf.last_audit_time < override_earliest_time):
                    override_shelves.append(shelf)

            for shelf in override_shelves + global_setting_query.fetch():
                try:
                    events.raise_event(event_name='shelf_needs_audit',
                                       shelf=shelf)
                except events.EventActionsError as err:
                    # We catch the event error and only log that error so that a single
                    # shelf will not disrupt requesting audit on all other shelves that
                    # may need to be audited.
                    logging.error(
                        'Failed to request audit for shelf %r because the following '
                        'error occurred: %s', shelf.identifier, err)

        else:
            logging.warning('Shelf audit reminders are currently disabled.')
Ejemplo n.º 10
0
    def test_raise_event(self, mock_geteventactions, mock_taskqueueadd,
                         mock_loginfo):
        """Tests raising an Action if the Event is configured for Actions."""

        self.testbed.raise_event_patcher.stop(
        )  # Disable patcher; use real method.

        # No Actions configured for the Event.
        mock_geteventactions.return_value = []
        events.raise_event('sample_event')
        mock_loginfo.assert_called_with(events._NO_ACTIONS_MSG, 'sample_event')

        mock_geteventactions.return_value = ['action1', 'action2']
        test_device = device_model.Device(chrome_device_id='4815162342',
                                          serial_number='123456')
        test_shelf = shelf_model.Shelf(capacity=42, location='Helpdesk 123')
        expected_payload1 = pickle.dumps({
            'action_name': 'action1',
            'device': test_device,
            'shelf': test_shelf
        })
        expected_payload2 = pickle.dumps({
            'action_name': 'action2',
            'device': test_device,
            'shelf': test_shelf
        })

        events.raise_event('sample_event',
                           device=test_device,
                           shelf=test_shelf)

        self.testbed.raise_event_patcher.start(
        )  # Because cleanup will stop().

        expected_calls = [
            mock.call(queue_name='process-action',
                      payload=expected_payload1,
                      target='default'),
            mock.call(queue_name='process-action',
                      payload=expected_payload2,
                      target='default')
        ]
        mock_taskqueueadd.assert_has_calls(expected_calls)
 def get(self):
     """Process an Action task with the correct Action class."""
     custom_events = event_models.CustomEvent.get_all_enabled()
     for custom_event in custom_events:
         for entity in custom_event.get_matching_entities():
             device = (entity
                       if custom_event.model.lower() == 'device' else None)
             shelf = (entity
                      if custom_event.model.lower() == 'shelf' else None)
             try:
                 events.raise_event(event_name=custom_event.name,
                                    device=device,
                                    shelf=shelf)
             except events.EventActionsError as err:
                 # We log the error instead of raising an error so that we do not
                 # disrupt the handler for executing other devices/shelves when one of
                 # them fails.
                 logging.error(
                     'The following error occurred while trying to perform the event '
                     '%r: %s', custom_event.name, err)
Ejemplo n.º 12
0
    def device_audit_check(self):
        """Checks a device to make sure it passes all prechecks for audit.

    Raises:
      DeviceNotEnrolledError: when a device is not enrolled in the application.
      UnableToMoveToShelfError: when a deivce can not be checked into a shelf.
      DeviceAuditError:when a device encounters an error during auditing
    """
        if not self.enrolled:
            raise DeviceNotEnrolledError(DEVICE_NOT_ENROLLED_MSG %
                                         self.identifier)
        if self.damaged:
            raise UnableToMoveToShelfError(_DEVICE_DAMAGED_MSG %
                                           self.identifier)
        try:
            events.raise_event('device_audit', device=self)
        except events.EventActionsError as err:
            # For any action that is implemented for device_audit that is
            # required for the rest of the logic an error should be raised.
            # If all actions are not required, eg sending a notification email only,
            # the error should only be logged.
            raise DeviceAuditEventError(err)
Ejemplo n.º 13
0
  def unenroll(self, user_email):
    """Unenrolls a device, removing it from the Grab n Go program.

    This moves the device to the root Chrome OU, however it does not change its
    losr or locked attributes, nor does it unlock it if it's locked (i.e.,
    disabled in the Directory API).

    Args:
      user_email: str, email address of the user making the request.

    Returns:
      The unenrolled device.

    Raises:
      FailedToUnenrollError: raised when moving the device's OU fails.
    """
    unenroll_ou = config_model.Config.get('unenroll_ou')
    directory_client = directory.DirectoryApiClient(user_email)
    try:
      directory_client.move_chrome_device_org_unit(
          device_id=self.chrome_device_id, org_unit_path=unenroll_ou)
    except directory.DirectoryRPCError as err:
      raise FailedToUnenrollError(
          _FAILED_TO_MOVE_DEVICE_MSG % (self.identifier, unenroll_ou, str(err)))
    self.enrolled = False
    self.due_date = None
    self.shelf = None
    self.assigned_user = None
    self.assignment_date = None
    self.current_ou = unenroll_ou
    self.ou_changed_date = datetime.datetime.utcnow()
    self.mark_pending_return_date = None
    self.last_reminder = None
    self.next_reminder = None

    self.put()
    self.stream_to_bq(user_email, 'Unenrolling device.')
    events.raise_event('device_unenroll', device=self)
    return self
Ejemplo n.º 14
0
    def unenroll(self, user_email):
        """Unenrolls a device, removing it from the Grab n Go program.

    This moves the device to the root Chrome OU, however it does not change its
    losr or locked attributes, nor does it unlock it if it's locked (i.e.,
    disabled in the Directory API).

    Args:
      user_email: str, email address of the user making the request.

    Returns:
      The unenrolled device.

    Raises:
      FailedToUnenrollError: raised when moving the device's OU fails.
    """
        if self.assigned_user:
            self._loan_return(user_email)
        unenroll_ou = config_model.Config.get('unenroll_ou')
        directory_client = directory.DirectoryApiClient(user_email)
        try:
            directory_client.move_chrome_device_org_unit(
                device_id=self.chrome_device_id, org_unit_path=unenroll_ou)
        except directory.DirectoryRPCError as err:
            raise FailedToUnenrollError(
                _FAILED_TO_MOVE_DEVICE_MSG %
                (self.identifier, unenroll_ou, str(err)))
        self.enrolled = False
        self.due_date = None
        self.shelf = None
        self.assigned_user = None
        self.assignment_date = None
        self.current_ou = unenroll_ou
        self.ou_changed_date = datetime.datetime.utcnow()
        self.mark_pending_return_date = None
        self.last_reminder = None
        self.next_reminder = None
        event_action = 'device_unenroll'
        try:
            self = events.raise_event(event_action, device=self)
        except events.EventActionsError as err:
            # For any action that is implemented for device_unenroll that is required
            # for the rest of the logic an error should be raised. If all actions are
            # not required, eg sending a notification email only, the error should be
            # logged.
            logging.error(_EVENT_ACTION_ERROR_MSG, event_action, err)
        self.put()
        self.stream_to_bq(user_email,
                          'Unenrolling device %s.' % self.identifier)
        return self
Ejemplo n.º 15
0
  def _loan_return(self, user_email):
    """Returns a device in a loan.

    Args:
      user_email: str, user_email of the user initiating the return.

    Returns:
      The key of the datastore record.
    """
    events.raise_event('device_loan_return', device=self)
    if self.lost:
      self.lost = False
    if self.locked:
      self.unlock(user_email)
    self.assigned_user = None
    self.assignment_date = None
    self.due_date = None
    self.mark_pending_return_date = None
    self.move_to_default_ou(user_email=user_email)
    self.last_reminder = None
    self.next_reminder = None
    self.put()
    self.stream_to_bq(user_email, 'Marking device as returned.')
    return self.key
Ejemplo n.º 16
0
  def disable(self, user_email):
    """Marks a shelf as disabled.

    Args:
      user_email: str, email of the user disabling the shelf.
    """
    self.enabled = False
    logging.info(_DISABLE_MSG, self.identifier)
    event_action = 'shelf_disable'
    try:
      self = events.raise_event(event_action, shelf=self)
    except events.EventActionsError as err:
      # For any action that is implemented for shelf_disable that is required
      # for the rest of the logic an error should be raised. If all
      # actions are not required, eg sending a notification email only,
      # the error should only be logged.
      logging.error(_EVENT_ACTION_ERROR_MSG, event_action, err)
    self.put()
    self.stream_to_bq(user_email, _DISABLE_MSG % self.identifier)
Ejemplo n.º 17
0
  def loan_assign(self, user_email):
    """Assigns a device to a user.

    Args:
      user_email: str, email address of the user to whom the device should be
          assigned.

    Returns:
      The key of the datastore record.

    Raises:
      AssignmentError: if the device is not enrolled.
    """
    if not self.enrolled:
      raise AssignmentError(
          'Cannot assign an unenrolled device %s.' % self.identifier)

    if self.assigned_user and self.assigned_user != user_email:
      self._loan_return(user_email)

    self.assigned_user = user_email
    self.assignment_date = datetime.datetime.utcnow()
    self.mark_pending_return_date = None
    self.shelf = None
    self.due_date = calculate_return_dates(self.assignment_date).default
    self.move_to_default_ou(user_email=user_email)
    event_action = 'device_loan_assign'
    try:
      self = events.raise_event(event_action, device=self)
    except events.EventActionsError as err:
      # For any action that is implemented for device_loan_assign that is
      # required for the rest of the logic an error should be raised.
      # If all actions are not required, eg sending a notification email only,
      # the error should only be logged.
      logging.error(_EVENT_ACTION_ERROR_MSG, event_action, err)
    self.put()
    self.stream_to_bq(
        user_email, 'Beginning new loan for user %s with device %s.' %
        (self.assigned_user, self.identifier))
    return self.key
Ejemplo n.º 18
0
  def audit(self, user_email, num_of_devices):
    """Marks a shelf audited.

    Args:
      user_email: str, email of the user auditing the shelf.
      num_of_devices: int, the number of devices on shelf.
    """
    self.last_audit_time = datetime.datetime.utcnow()
    self.last_audit_by = user_email
    self.audit_requested = False
    logging.info(_AUDIT_MSG, self.identifier, num_of_devices)
    event_action = 'shelf_audited'
    try:
      self = events.raise_event(event_action, shelf=self)
    except events.EventActionsError as err:
      # For any action that is implemented for shelf_audited that is required
      # for the rest of the logic an error should be raised. If all
      # actions are not required, eg sending a notification email only,
      # the error should only be logged.
      logging.error(_EVENT_ACTION_ERROR_MSG, event_action, err)
    self.put()
    self.stream_to_bq(
        user_email, _AUDIT_MSG % (self.identifier, num_of_devices))
Ejemplo n.º 19
0
    def test_raise_event(self, mock_loadactions, mock_getactionsforevent,
                         mock_taskqueueadd, mock_logwarn, mock_logerror):
        """Tests raising an Action if the Event is configured for Actions."""
        self.testbed.raise_event_patcher.stop(
        )  # Disable patcher; use real method.

        # No Actions configured for the Event.
        mock_getactionsforevent.return_value = []
        events.raise_event('sample_event')
        mock_logwarn.assert_called_with(events._NO_ACTIONS_MSG, 'sample_event')

        # Everything is running smoothly.
        def side_effect1(device=None):
            """Side effect for sync action's run method that returns the model."""
            return device

        mock_sync_action = mock.Mock()
        mock_sync_action.run.side_effect = side_effect1
        mock_loadactions.return_value = {
            'sync': {
                'sync_action': mock_sync_action
            },
            'async': {
                'async_action1': 'fake_async_action1',
                'async_action2': 'fake_async_action2',
                'async_action3': 'fake_async_action3',
            }
        }
        mock_getactionsforevent.return_value = [
            'sync_action', 'async_action3', 'async_action1', 'async_action2'
        ]
        test_device = device_model.Device(chrome_device_id='4815162342',
                                          serial_number='123456')

        expected_async_payload = pickle.dumps({
            'async_actions':
            ['async_action1', 'async_action2', 'async_action3'],
            'device':
            test_device
        })

        events.raise_event('sample_event', device=test_device)

        expected_calls = [
            mock.call(queue_name='process-action',
                      payload=expected_async_payload,
                      target='default'),
        ]
        mock_taskqueueadd.assert_has_calls(expected_calls)
        mock_sync_action.run.assert_called_once_with(device=test_device)

        # A sync action raises a catchable exception.
        mock_sync_action.reset_mock()
        mock_logerror.reset_mock()
        mock_getactionsforevent.reset_mock()
        mock_loadactions.reset_mock()

        def side_effect2(device=None):
            """Side effect for sync action's run method that returns the model."""
            del device  # Unused.
            raise base_action.BadDeviceError('Found a bad attribute.')

        mock_sync_action.run.side_effect = side_effect2
        mock_loadactions.return_value = {
            'sync': {
                'sync_action': mock_sync_action
            },
            'async': {}
        }
        mock_getactionsforevent.return_value = ['sync_action']

        with self.assertRaises(events.EventActionsError):
            events.raise_event('sample_event', device=test_device)
            self.assertLen(mock_logerror.mock_calls, 1)

        self.testbed.raise_event_patcher.start(
        )  # Because cleanup will stop().
Ejemplo n.º 20
0
    def enroll(cls,
               user_email,
               location,
               capacity,
               friendly_name=None,
               latitude=None,
               longitude=None,
               altitude=None,
               responsible_for_audit=None,
               audit_notification_enabled=True,
               audit_interval_override=None):
        """Creates a new shelf or reactivates an existing one.

    Args:
      user_email: str, email of the user enrolling the shelf.
      location: str, location description of a shelf.
      capacity: int, maximum shelf capacity.
      friendly_name: str, optional, friendly name for a shelf.
      latitude: float, optional, latitude. Required if long provided.
      longitude: float, optional, longitude. Required if lat provided.
      altitude: int, optional, altitude of the shelf.
      responsible_for_audit: str, optional, string email (if email enabled) or
          other modifier (eg ticket queue) for the party responsible for
          auditing this shelf.
      audit_notification_enabled: bool, optional, enable or disable shelf audit
          notifications.
      audit_interval_override: An integer for the number of hours to allow a
          shelf to remain unaudited, overriding the global shelf_audit_interval
          setting.

    Returns:
      The newly created or reactivated shelf.

    Raises:
      EnrollmentError: If enrollment fails.
    """
        if bool(latitude) ^ bool(longitude):
            raise EnrollmentError(_LAT_LONG_MSG)

        shelf = cls.get(location=location, friendly_name=friendly_name)
        if shelf:
            shelf.enabled = True
            shelf.capacity = capacity
            shelf.friendly_name = friendly_name
            shelf.altitude = altitude
            shelf.responsible_for_audit = responsible_for_audit
            if latitude is not None and longitude is not None:
                shelf.lat_long = ndb.GeoPt(latitude, longitude)
            shelf.audit_interval_override = audit_interval_override
            logging.info(_REACTIVATE_MSG, shelf.identifier)
        else:
            shelf = cls(location=location,
                        capacity=capacity,
                        friendly_name=friendly_name,
                        altitude=altitude,
                        audit_notification_enabled=audit_notification_enabled,
                        responsible_for_audit=responsible_for_audit,
                        audit_interval_override=audit_interval_override)
            if latitude is not None and longitude is not None:
                shelf.lat_long = ndb.GeoPt(latitude, longitude)
            logging.info(_CREATE_NEW_SHELF_MSG, shelf.identifier)
        shelf = events.raise_event('shelf_enroll', shelf=shelf)
        shelf.put()
        shelf.stream_to_bq(user_email, _ENROLL_MSG % shelf.identifier)
        return shelf
Ejemplo n.º 21
0
  def enroll(cls, user_email, serial_number=None, asset_tag=None):
    """Enrolls a new device.

    Args:
      user_email: str, email address of the user making the request.
      serial_number: str, serial number of the device.
      asset_tag: str, optional, asset tag of the device.

    Returns:
      The enrolled device object.

    Raises:
      DeviceCreationError: raised when moving the device's OU fails or when the
          directory API responds with incomplete information or if the device is
          not found in the directory API.
    """
    if serial_number:
      serial_number = serial_number.upper()
    if asset_tag:
      asset_tag = asset_tag.upper()
    device_identifier_mode = config_model.Config.get('device_identifier_mode')
    if not asset_tag and device_identifier_mode in (
        config_model.DeviceIdentifierMode.BOTH_REQUIRED,
        config_model.DeviceIdentifierMode.ASSET_TAG):
      raise datastore_errors.BadValueError(_ASSET_TAGS_REQUIRED_MSG)
    elif not serial_number and device_identifier_mode in (
        config_model.DeviceIdentifierMode.BOTH_REQUIRED,
        config_model.DeviceIdentifierMode.SERIAL_NUMBER):
      raise datastore_errors.BadValueError(_SERIAL_NUMBERS_REQUIRED_MSG)
    directory_client = directory.DirectoryApiClient(user_email)
    device = cls.get(serial_number=serial_number, asset_tag=asset_tag)
    now = datetime.datetime.utcnow()

    existing_device = bool(device)
    if existing_device:
      device = _update_existing_device(device, user_email, asset_tag)
    else:
      device = cls(serial_number=serial_number, asset_tag=asset_tag)

    identifier = serial_number or asset_tag
    logging.info('Enrolling device %s', identifier)
    try:
      device = events.raise_event('device_enroll', device=device)
    except events.EventActionsError as err:
      # For any action that is implemented for device_enroll that is required
      # for the rest of the logic an error should be raised. If all actions are
      # not required, eg sending a notification email only, the error should
      # only be logged.
      raise DeviceCreationError(err)
    if device.serial_number:
      serial_number = device.serial_number
    else:
      raise DeviceCreationError(
          'No serial number for device %s.' % identifier)

    if not existing_device:
      # If this implementation of the app can translate asset tags to serial
      # numbers, recheck for an existing device now that we may have the serial.
      if device_identifier_mode == (
          config_model.DeviceIdentifierMode.ASSET_TAG):
        device_by_serial = cls.get(serial_number=serial_number)
        if device_by_serial:
          device = _update_existing_device(
              device_by_serial, user_email, asset_tag)
          existing_device = True

    try:
      # Get a Chrome OS Device object as per
      # https://developers.google.com/admin-sdk/directory/v1/reference/chromeosdevices
      directory_device_object = directory_client.get_chrome_device_by_serial(
          serial_number)
    except directory.DeviceDoesNotExistError as err:
      raise DeviceCreationError(str(err))
    try:
      device.chrome_device_id = directory_device_object[directory.DEVICE_ID]
      device.current_ou = directory_device_object[directory.ORG_UNIT_PATH]
      device.device_model = directory_device_object[directory.MODEL]
    except KeyError:
      raise DeviceCreationError(_DIRECTORY_INFO_INCOMPLETE_MSG)

    try:
      directory_client.move_chrome_device_org_unit(
          device_id=directory_device_object[directory.DEVICE_ID],
          org_unit_path=constants.ORG_UNIT_DICT['DEFAULT'])
    except directory.DirectoryRPCError as err:
      raise DeviceCreationError(
          _FAILED_TO_MOVE_DEVICE_MSG % (
              serial_number, constants.ORG_UNIT_DICT['DEFAULT'], str(err)))
    device.current_ou = constants.ORG_UNIT_DICT['DEFAULT']
    device.ou_changed_date = now
    device.last_known_healthy = now
    device.put()
    device.stream_to_bq(user_email, 'Enrolling device %s.' % identifier)
    return device
Ejemplo n.º 22
0
  def enroll(cls, serial_number, user_email, asset_tag=None):
    """Enrolls a new device.

    Args:
      serial_number: str, serial number of the device.
      user_email: str, email address of the user making the request.
      asset_tag: str, optional, asset tag of the device.

    Returns:
      The enrolled device object.

    Raises:
      DeviceCreationError: raised when moving the device's OU fails or when the
          directory API responds with incomplete information or if the device is
          not found in the directory API.
    """
    directory_client = directory.DirectoryApiClient(user_email)
    device = cls.get(serial_number=serial_number)
    now = datetime.datetime.utcnow()
    was_lost_or_locked = False

    if device:
      logging.info('Previous device found, re-enrolling.')

      if device.locked:
        was_lost_or_locked = True
        device.unlock(user_email)
      if device.lost:
        was_lost_or_locked = True
        device.lost = False

      try:
        device.move_to_default_ou(user_email=user_email)
      except UnableToMoveToDefaultOUError as err:
        raise DeviceCreationError(str(err))
      device.enrolled = True
      device.asset_tag = asset_tag
      device.last_known_healthy = now
      device.mark_pending_return_date = None
    else:
      try:
        dir_device = directory_client.get_chrome_device_by_serial(serial_number)
      except directory.DeviceDoesNotExistError as err:
        raise DeviceCreationError(str(err))

      if dir_device[
          directory.ORG_UNIT_PATH] != constants.ORG_UNIT_DICT['DEFAULT']:
        try:
          directory_client.move_chrome_device_org_unit(
              device_id=dir_device[directory.DEVICE_ID],
              org_unit_path=constants.ORG_UNIT_DICT['DEFAULT'])
        except directory.DirectoryRPCError as err:
          raise DeviceCreationError(
              _FAILED_TO_MOVE_DEVICE_MSG % (
                  serial_number, constants.ORG_UNIT_DICT['DEFAULT'],
                  str(err)))

      try:
        device = cls(
            serial_number=serial_number,
            asset_tag=asset_tag,
            device_model=dir_device.get(directory.MODEL),
            last_known_healthy=now,
            current_ou=constants.ORG_UNIT_DICT['DEFAULT'],
            ou_changed_date=now,
            chrome_device_id=dir_device[directory.DEVICE_ID])
      except KeyError:
        raise DeviceCreationError(_DIRECTORY_INFO_INCOMPLETE_MSG)

    logging.info('Enrolling device %s', serial_number)
    device.put()
    device.stream_to_bq(user_email, 'Enrolling device.')

    if was_lost_or_locked:
      events.raise_event('device_enroll_lost_or_locked', device=device)
    else:
      events.raise_event('device_enroll', device=device)
    return device