def token_update(plist_data): current_app.logger.debug('TokenUpdate (UDID %s)', plist_data.get('UDID', None)) try: device = db.session.query(Device).filter( Device.udid == plist_data['UDID']).one() except NoResultFound: current_app.logger.debug( 'Device (UDID: %s) will be unenrolled because the database has no record of this device.', plist_data['UDID']) return abort( 410 ) # Ask the device to unenroll itself because we dont seem to have any records. # TODO: a TokenUpdate can either be for a device or a user (per OS X extensions) if 'UserID' in plist_data: device_user = DeviceUser() return 'OK' if not device.token: # First contact device.is_enrolled = True if hasattr(g, 'signers'): device_certificate = DeviceIdentityCertificate.from_crypto( g.signers[0]) db.session.add(device_certificate) device.certificate = device_certificate else: pass # TODO: if in debug mode this should not throw an exception to deal with cert troubleshooting device_enrolled.send(device) queue_full_inventory(device) device.tokenupdate_at = datetime.utcnow() device.push_magic = plist_data.get('PushMagic', None) device.topic = plist_data.get('Topic', None) device.token = plist_data.get('Token', None) device.unlock_token = plist_data.get('UnlockToken', None) device.last_seen = datetime.now() db.session.commit() # TODO: DRY command = DBCommand.next_command(device) if not command: current_app.logger.info('no further MDM commands for device=%d', device.id) return '' # mark this command as being in process right away to (try) to avoid # any race conditions with mutliple MDM commands from the same device # at a time #command.set_processing() #db.session.commit() # Re-hydrate the command class based on the persisted model containing the request type and the parameters # that were given to generate the command cmd = Command.new_request_type(command.request_type, command.parameters, command.uuid) # get command dictionary representation (e.g. the full command to send) output_dict = cmd.to_dict() current_app.logger.info('sending %s MDM command class=%s to device=%d', cmd.request_type, command.request_type, device.id) current_app.logger.debug(output_dict) command.status = CommandStatus.Sent command.sent_at = datetime.utcnow() db.session.commit() return plistify(output_dict)
def mdm(): """MDM connection endpoint. Most MDM communication is via this URI. This endpoint delivers and handles incoming command responses. Such as: `Idle`, `NotNow`, `Acknowledged`. :reqheader Content-Type: application/x-apple-aspen-mdm; charset=UTF-8 :reqheader Mdm-Signature: BASE64-encoded CMS Detached Signature of the message. (if `SignMessage` was true) :resheader Content-Type: application/xml; charset=UTF-8 :status 200: With an empty body, no commands remaining, or plist contents of next command. :status 400: Invalid data submitted :status 410: User channel capability not available. """ # TODO: proper identity verification, for now just matching on UDID try: device = db.session.query(Device).filter( Device.udid == g.plist_data['UDID']).one() except NoResultFound: current_app.logger.info( "An unmanaged device (UDID %s), tried to check in with us, rejecting.", g.plist_data['UDID']) return abort(410) # Unmanage devices that we dont have a record of if 'UserID' in g.plist_data: # Note that with DEP this is an opportune time to queue up an # application install for the /device/ despite this being a per-user # MDM command. this is becasue DEP appears to only allow apps to be # installed while a user is logged in. note also the undocumented # NotOnConsole key to (possibly) indicate that this is a UI login? current_app.logger.warn('per-user MDM command not yet supported') return '' if 'Status' not in g.plist_data: current_app.logger.error( 'invalid MDM request (no Status provided) from device id %d' % device.id) return abort(400, 'response does not contain Status') else: status = CommandStatus(g.plist_data['Status']) current_app.logger.info('device id=%d udid=%s processing status=%s', device.id, device.udid, status) device.last_seen = datetime.utcnow() db.session.commit() if current_app.config['DEBUG']: try: print(g.plist_data) except UnicodeEncodeError: print('Cannot DEBUG print plist request, unencodable characters') if status != CommandStatus.Idle: # this device is responding to an earlier command. if 'CommandUUID' not in g.plist_data: current_app.logger.error('missing CommandUUID for non-Idle status') abort(400, 'response does not contain CommandUUID') try: command = DBCommand.find_by_uuid(g.plist_data['CommandUUID']) command.status = status command.acknowledged_at = datetime.utcnow() db.session.commit() # Re-hydrate the command class based on the persisted model containing the request type and the parameters # that were given to generate the command cmd = Command.new_request_type(command.request_type, command.parameters, command.uuid) # route the response by the handler type corresponding to that command command_router.handle(cmd, device, g.plist_data) except NoResultFound: current_app.logger.warning('no record of command uuid=%s', g.plist_data['CommandUUID']) if status == CommandStatus.NotNow: current_app.logger.warn( 'NotNow status received, forgoing any further commands') return '' command = DBCommand.next_command(device) if not command: current_app.logger.info('no further MDM commands for device=%d', device.id) return '' # mark this command as being in process right away to (try) to avoid # any race conditions with mutliple MDM commands from the same device # at a time #command.set_processing() #db.session.commit() # Re-hydrate the command class based on the persisted model containing the request type and the parameters # that were given to generate the command cmd = Command.new_request_type(command.request_type, command.parameters, command.uuid) # get command dictionary representation (e.g. the full command to send) output_dict = cmd.to_dict() current_app.logger.info('sending %s MDM command class=%s to device=%d', cmd.request_type, command.request_type, device.id) current_app.logger.debug(output_dict) command.status = CommandStatus.Sent command.sent_at = datetime.utcnow() db.session.commit() return plistify(output_dict)