Exemplo n.º 1
0
    def _set_calibration_for_data_product(self, dp_obj, dev_cfg):
        from ion.util.direct_coverage_utils import DirectCoverageAccess
        from coverage_model import SparseConstantType

        log.debug("Setting calibration for data product '%s'", dp_obj.name)
        dataset_ids, _ = self.rr.find_objects(dp_obj, PRED.hasDataset, id_only=True)
        publisher = EventPublisher(OT.InformationContentModifiedEvent)
        if not dataset_ids:
            data_product_management = DataProductManagementServiceProcessClient(process=self)
            log.debug(" Creating dataset for data product %s", dp_obj.name)
            data_product_management.create_dataset_for_data_product(dp_obj._id)
            dataset_ids, _ = self.rr.find_objects(dp_obj, PRED.hasDataset, id_only=True)
            if not dataset_ids:
                raise NotFound('No datasets were found for this data product, ensure that it was created')
        for dataset_id in dataset_ids:
            # Synchronize with ingestion
            with DirectCoverageAccess() as dca:
                cov = dca.get_editable_coverage(dataset_id)
                # Iterate over the calibrations
                for cal_name, contents in dev_cfg.iteritems():
                    if cal_name in cov.list_parameters() and isinstance(cov.get_parameter_context(cal_name).param_type, SparseConstantType):
                        value = float(contents['value'])
                        log.info(' Updating Calibrations for %s in %s', cal_name, dataset_id)
                        cov.set_parameter_values(cal_name, value)
                    else:
                        log.warn(" Calibration %s not found in dataset", cal_name)
                publisher.publish_event(origin=dataset_id, description="Calibrations Updated")
        publisher.close()
        log.info("Calibration set for data product '%s' in %s coverages", dp_obj.name, len(dataset_ids))
Exemplo n.º 2
0
 def _publish_access_event(self, access_type, data_product_id=None, access_params=None):
     try:
         pub = EventPublisher(OT.InformationContentAccessedEvent, process=self)
         event_data = dict(origin_type=RT.DataProduct,
                           origin=data_product_id or "",
                           sub_type=access_type,
                           access_params=access_params or {})
         pub.publish_event(**event_data)
     except Exception as ex:
         log.exception("Error publishing InformationContentAccessedEvent for data product: %s", data_product_id)
Exemplo n.º 3
0
def process_oms_event():
    if not request.data:
        log.warning('process_oms_event: invalid OMS event payload: %r', request.data)
        return gateway_json_response(OMS_BAD_REQUEST_RESPONSE)

    payload = json_loads(str(request.data))
    if not isinstance(payload, list):
        log.warning('process_oms_event: invalid OMS event payload: '
                    'expecting array but got: %r', payload)
        return gateway_json_response(OMS_BAD_REQUEST_RESPONSE)

    log.debug('process_oms_event: payload=%s', payload)

    event_publisher = EventPublisher()

    for obj in payload:
        for k in ['event_id', 'platform_id', 'message']:
            if k not in obj:
                log.warning('process_oms_event: invalid OMS event: %r missing. '
                            'Received object: %s', k, obj)
                #return gateway_json_response(OMS_BAD_REQUEST_RESPONSE)

        # note the the external event_id is captured in the sub_type field:
        evt = dict(
            event_type     = 'OMSDeviceStatusEvent',
            origin_type    = 'OMS Platform',
            origin         = obj.get('platform_id', 'platform_id NOT PROVIDED'),
            sub_type       = obj.get('event_id', 'event_id NOT PROVIDED'),
            description    = obj.get('message', ''),
            status_details = obj)
        try:
            event_publisher.publish_event(**evt)
            log.debug('process_oms_event: published: %s', evt)

        except Exception as e:
            log.exception('process_oms_event: could not publish OMS event: %s', evt)

    return gateway_json_response(OMS_ACCEPTED_RESPONSE)
    def test_procs(self):
        # Start procs
        config = {}
        pid1 = self.container.spawn_process("hello1", __name__, "CoordinatedProcess", config=config)
        pid2 = self.container.spawn_process("hello2", __name__, "CoordinatedProcess", config=config)
        pid3 = self.container.spawn_process("hello3", __name__, "CoordinatedProcess", config=config)

        # Wait for them to be ready
        gevent.sleep(0.3)

        # Call service
        evt_pub = EventPublisher(event_type=OT.ResourceCommandEvent)
        hc = HelloServiceClient()
        end_time = get_ion_ts_millis() + 4000
        counter = 0
        while get_ion_ts_millis() < end_time:
            counter += 1
            res = hc.hello("foo")
            evt_pub.publish_event(origin=str(counter))
            gevent.sleep(random.random()*0.1)

        # Wait for them to be finish
        gevent.sleep(0.3)

        # Wrap up
        self.container.terminate_process(pid1)
        self.container.terminate_process(pid2)
        self.container.terminate_process(pid3)

        evt_count = CoordinatedProcess.evt_count
        log.info("Counters: %s", evt_count)

        self.assertEqual(evt_count["total"], 3*counter)
        master_evts = evt_count[pid1] + evt_count[pid2] + evt_count[pid3]
        self.assertLessEqual(master_evts, counter)
        if master_evts < counter:
            log.info("Lost %s events - no functioning master", counter - master_evts)
    def on_start(self):
        self.ION_NOTIFICATION_EMAIL_ADDRESS = CFG.get_safe('server.smtp.sender')

        # Create an event processor
        self.event_processor = EmailEventProcessor()

        # Dictionaries that maintain information asetting_up_smtp_clientbout users and their subscribed notifications
        self.user_info = {}

        # The reverse_user_info is calculated from the user_info dictionary
        self.reverse_user_info = {}

        self.event_publisher = EventPublisher(process=self)

        self.start_time = get_ion_ts()

        #------------------------------------------------------------------------------------
        # Create an event subscriber for Reload User Info events
        #------------------------------------------------------------------------------------

        def reload_user_info(event_msg, headers):
            """
            Callback method for the subscriber to ReloadUserInfoEvent
            """

            notification_id =  event_msg.notification_id
            log.debug("(UNS instance) received a ReloadNotificationEvent. The relevant notification_id is %s" % notification_id)

            try:
                self.user_info = self.load_user_info()
            except NotFound:
                log.warning("ElasticSearch has not yet loaded the user_index.")

            self.reverse_user_info =  calculate_reverse_user_info(self.user_info)

            log.debug("(UNS instance) After a reload, the user_info: %s" % self.user_info)
            log.debug("(UNS instance) The recalculated reverse_user_info: %s" % self.reverse_user_info)

        # the subscriber for the ReloadUSerInfoEvent
        self.reload_user_info_subscriber = EventSubscriber(
            event_type=OT.ReloadUserInfoEvent,
            origin='UserNotificationService',
            callback=reload_user_info
        )
        self.add_endpoint(self.reload_user_info_subscriber)
 def on_start(self):
     self.event_pub = EventPublisher(process=self)
class PolicyManagementService(BasePolicyManagementService):

    event_pub = None

    def on_start(self):
        self.event_pub = EventPublisher(process=self)

    # -------------------------------------------------------------------------
    # Policy management

    def create_resource_access_policy(self, resource_id='', policy_name='', description='', policy_rule='', ordinal=0):
        """Boilerplate operation for creating an access policy for a specific resource.
        """
        if not resource_id:
            raise BadRequest("The resource_id argument is missing")
        if not policy_name:
            raise BadRequest("The policy_name argument is missing")
        if not description:
            raise BadRequest("The description argument is missing")
        if not policy_rule:
            raise BadRequest("The policy_rule argument is missing")

        policy_obj = IonObject(RT.Policy, name=policy_name, description=description,
                               policy_type=PolicyTypeEnum.RESOURCE_ACCESS,
                               definition=policy_rule, ordinal=ordinal,
                               details=IonObject(OT.ResourceAccessPolicyDetails, resource_id=resource_id))
        policy_id = self.create_policy(policy_obj)
        self._add_resource_policy(resource_id, policy_id, publish_event=False)

        return policy_id

    def create_service_access_policy(self, service_name='', policy_name='', description='', policy_rule='', ordinal=0):
        """Boilerplate operation for creating an access policy for a specific service.
        """
        if not service_name:
            raise BadRequest("The service_name argument is missing")
        if not policy_name:
            raise BadRequest("The policy_name argument is missing")
        if not description:
            raise BadRequest("The description argument is missing")
        if not policy_rule:
            raise BadRequest("The policy_rule argument is missing")

        policy_obj = IonObject(RT.Policy, name=policy_name, description=description,
                               policy_type=PolicyTypeEnum.SERVICE_ACCESS,
                               definition=policy_rule, ordinal=ordinal,
                               details=IonObject(OT.ServiceAccessPolicyDetails, service_name=service_name))
        return self.create_policy(policy_obj)

    def create_common_service_access_policy(self, policy_name='', description='', policy_rule='', ordinal=0):
        """Boilerplate operation for creating a service access policy common to all services.
        """
        if not policy_name:
            raise BadRequest("The policy_name argument is missing")
        if not description:
            raise BadRequest("The description argument is missing")
        if not policy_rule:
            raise BadRequest("The policy_rule argument is missing")

        policy_obj = IonObject(RT.Policy, name=policy_name, description=description,
                               policy_type=PolicyTypeEnum.COMMON_SERVICE_ACCESS,
                               definition=policy_rule, ordinal=ordinal,)
        return self.create_policy(policy_obj)


    def add_process_operation_precondition_policy(self, process_id='', op='', policy_content=''):
        """Boilerplate operation for adding a precondition policy for a specific process operation.
        The precondition method must return a tuple (boolean, string).
        """
        if not process_id:
            raise BadRequest("The process_id argument is missing")
        if not op:
            raise BadRequest("The op argument is missing")
        if not policy_content:
            raise BadRequest("The policy_content argument is missing")

        policy_name = "Process_" + process_id + "_" + op + "_Precondition_Policies"
        policies, _ = self.clients.resource_registry.find_resources(restype=RT.Policy, name=policy_name)
        if policies:
            # Update existing policy by adding to list
            if len(policies) > 1:
                raise Inconsistent('There should only be one Policy object per process and operation')
            policy_obj = policies[0]
            if policy_obj.details.type_ != OT.ProcessOperationPreconditionPolicyDetails or policy_obj.details.op != op:
                raise Inconsistent('The Policy %s does not match the requested process operation %s: %s' % (
                        policy_obj.name, process_id, op))

            policy_obj.details.preconditions.append(policy_content)
            self.update_policy(policy_obj)

            return policy_obj._id

        else:
            policy_obj = IonObject(RT.Policy, name=policy_name,
                                   description='List of operation precondition policies for process ' + process_id,
                                   policy_type=PolicyTypeEnum.PROC_OP_PRECOND,
                                   details=IonObject(OT.ProcessOperationPreconditionPolicyDetails,
                                                     process_id=process_id, op=op, preconditions=[policy_content]))
            return self.create_policy(policy_obj)

    def add_service_operation_precondition_policy(self, service_name='', op='', policy_content=''):
        """Boilerplate operation for adding a precondition policy for a specific service operation.
        The precondition method must return a tuple (boolean, string).
        """
        if not service_name:
            raise BadRequest("The service_name argument is missing")
        if not op:
            raise BadRequest("The op argument is missing")
        if not policy_content:
            raise BadRequest("The policy_content argument is missing")

        policy_name = "Service_" + service_name + "_" + op + "_Precondition_Policies"
        policies, _ = self.clients.resource_registry.find_resources(restype=RT.Policy, name=policy_name)
        if policies:
            # Update existing policy by adding to list
            if len(policies) > 1:
                raise Inconsistent('There should only be one Policy object per service and operation')
            policy_obj = policies[0]
            if policy_obj.details.type_ != OT.ServiceOperationPreconditionPolicyDetails or policy_obj.details.op != op:
                raise Inconsistent('The Policy %s does not match the requested service operation %s: %s' % (
                        policy_obj.name, service_name, op))

            policy_obj.details.preconditions.append(policy_content)
            self.update_policy(policy_obj)

            return policy_obj._id

        else:
            policy_obj = IonObject(RT.Policy, name=policy_name,
                                   description='List of operation precondition policies for service ' + service_name,
                                   policy_type=PolicyTypeEnum.SERVICE_OP_PRECOND,
                                   details=IonObject(OT.ProcessOperationPreconditionPolicyDetails,
                                                     service_name=service_name, op=op, preconditions=[policy_content]))
            return self.create_policy(policy_obj)

    def create_policy(self, policy=None):
        """Persists the provided Policy object. Returns the policy id.
        """
        self._validate_resource_obj("policy", policy, RT.Policy, checks="noid,name")
        if not is_basic_identifier(policy.name):
            raise BadRequest("The policy name '%s' can only contain alphanumeric and underscore characters" % policy.name)

        try:
            # If there is a policy_rule field then try to add the policy name and description to the rule text
            if policy.definition:
                rule_tokens = dict(rule_id=policy.name, description=policy.description)
                policy.definition = policy.definition.format(**rule_tokens)

        except Exception as e:
            raise Inconsistent("Missing the elements in the policy rule to set the description: " + e.message)

        policy_id, _ = self.clients.resource_registry.create(policy)
        policy._id = policy_id

        log.debug('Policy created: ' + policy.name)
        self._publish_policy_event(policy)

        return policy_id

    def update_policy(self, policy=None):
        """Updates the provided Policy object.
        """
        self._validate_resource_obj("policy", policy, RT.Policy, checks="id,name")
        if not is_basic_identifier(policy.name):
            raise BadRequest("The policy name '%s' can only contain alphanumeric and underscore characters" % policy.name)

        self.clients.resource_registry.update(policy)

        self._publish_policy_event(policy)

    def read_policy(self, policy_id=''):
        """Returns the Policy object for the specified policy id.
        Throws exception if id does not match any persisted Policy
        objects.
        """
        policy_obj = self._validate_resource_id("policy_id", policy_id, RT.Policy)

        return policy_obj

    def delete_policy(self, policy_id=''):
        """For now, permanently deletes Policy object with the specified
        id. Throws exception if id does not match any persisted Policy.
        """
        policy_obj = self._validate_resource_id("policy_id", policy_id, RT.Policy)

        res_list = self._find_resources_for_policy(policy_id)
        for res in res_list:
            self._remove_resource_policy(res, policy_obj)

        self.clients.resource_registry.delete(policy_id)

        self._publish_policy_event(policy_obj, delete_policy=True)

    def enable_policy(self, policy_id=''):
        """Sets a flag to enable the use of the policy
        """
        policy_obj = self._validate_resource_id("policy_id", policy_id, RT.Policy)
        policy_obj.enabled = True
        self.update_policy(policy_obj)

    def disable_policy(self, policy_id=''):
        """Resets a flag to disable the use of the policy
        """
        policy_obj = self._validate_resource_id("policy_id", policy_id, RT.Policy)
        policy_obj.enabled = False
        self.update_policy(policy_obj)


    def add_resource_policy(self, resource_id='', policy_id=''):
        """Associates a policy to a specific resource
        """
        resource_obj, policy_obj = self._add_resource_policy(resource_id, policy_id)
        return True

    def _add_resource_policy(self, resource_id, policy_id, publish_event=True):
        """Removing a policy resource association and publish event for containers to update
        """
        resource_obj = self._validate_resource_id("resource_id", resource_id)
        policy_obj = self._validate_resource_id("policy_id", policy_id, RT.Policy)

        self.clients.resource_registry.create_association(resource_obj, PRED.hasPolicy, policy_obj)

        # Publish an event that the resource policy has changed
        if publish_event:
            self._publish_resource_policy_event(policy_obj, resource_obj)

        return resource_obj, policy_obj

    def remove_resource_policy(self, resource_id='', policy_id=''):
        """Removes an association for a policy to a specific resource
        """
        resource_obj = self._validate_resource_id("resource_id", resource_id)
        policy_obj = self._validate_resource_id("policy_id", policy_id, RT.Policy)

        self._remove_resource_policy(resource_obj, policy_obj)
        return True

    def _remove_resource_policy(self, resource, policy):
        aid = self.clients.resource_registry.get_association(resource, PRED.hasPolicy, policy)
        self.clients.resource_registry.delete_association(aid)

        # Publish an event that the resource policy has changed
        self._publish_resource_policy_event(policy, resource)


    def _publish_policy_event(self, policy, delete_policy=False):
        if policy.policy_type == PolicyTypeEnum.COMMON_SERVICE_ACCESS:
            self._publish_service_policy_event(policy, delete_policy)
        elif policy.policy_type == PolicyTypeEnum.SERVICE_ACCESS or policy.policy_type == PolicyTypeEnum.SERVICE_OP_PRECOND:
            self._publish_service_policy_event(policy, delete_policy)
        else:
            # Need to publish an event that a policy has changed for any associated resource
            res_list = self._find_resources_for_policy(policy._id)
            for res in res_list:
                self._publish_resource_policy_event(policy, res, delete_policy)


    def _publish_resource_policy_event(self, policy, resource, delete_policy=False):
        if self.event_pub:
            event_data = dict()
            event_data['origin_type'] = 'Resource_Policy'
            event_data['description'] = 'Updated Resource Policy'
            event_data['resource_id'] = resource._id
            event_data['resource_type'] = resource.type_
            event_data['resource_name'] = resource.name
            event_data['sub_type'] = 'DeletePolicy' if delete_policy else ''

            self.event_pub.publish_event(event_type='ResourcePolicyEvent', origin=policy._id, **event_data)

    def _publish_related_resource_policy_event(self, policy, resource_id, delete_policy=False):
        if self.event_pub:
            event_data = dict()
            event_data['origin_type'] = 'Resource_Policy'
            event_data['description'] = 'Updated Related Resource Policy'
            event_data['resource_id'] = resource_id
            event_data['sub_type'] = 'DeletePolicy' if delete_policy else ''

            self.event_pub.publish_event(event_type='RelatedResourcePolicyEvent', origin=policy._id, **event_data)

    def _publish_service_policy_event(self, policy, delete_policy=False):
        if self.event_pub:
            event_data = dict()
            event_data['origin_type'] = 'Service_Policy'
            event_data['description'] = 'Updated Service Policy'
            event_data['sub_type'] = 'DeletePolicy' if delete_policy else ''
            event_data['service_name'] = getattr(policy.details, 'service_name', "")

            if policy.policy_type == PolicyTypeEnum.SERVICE_OP_PRECOND:
                event_data['op'] = policy.details.op

            self.event_pub.publish_event(event_type='ServicePolicyEvent', origin=policy._id, **event_data)


    def find_resource_policies(self, resource_id=''):
        """Finds all policies associated with a specific resource
        """
        resource_obj = self._validate_resource_id("resource_id", resource_id)
        return self._find_resource_policies(resource_obj)

    def _find_resource_policies(self, resource, policy=None):
        policy_list, _ = self.clients.resource_registry.find_objects(resource, PRED.hasPolicy, policy)
        return policy_list

    def _find_resources_for_policy(self, policy_id=''):
        """Finds all resources associated with a specific policy
        """
        resource_list, _ = self.clients.resource_registry.find_subjects(None, PRED.hasPolicy, policy_id)
        return resource_list


    def get_active_resource_access_policy_rules(self, resource_id='', org_name=''):
        """Generates the set of all enabled access policies for the specified resource within
        the specified Org. If the org_name is not provided, then the root ION Org will be assumed.
        """
        # TODO - extend to handle Org specific service policies at some point.
        policy_list = []
        resource_obj = self._validate_resource_id("resource_id", resource_id)
        resource_id_list = self._get_related_resource_ids(resource_obj)
        if not resource_id_list:
            resource_id_list.append(resource_id)
        log.debug("Retrieving policies for resources: %s", resource_id_list)

        for res_id in resource_id_list:
            policy_set = self._find_resource_policies(res_id)

            for p in policy_set:
                if p.enabled and p.policy_type == PolicyTypeEnum.RESOURCE_ACCESS:
                    log.debug("Including policy: %s", p.name)
                    policy_list.append(p)

        policy_list.sort(key=lambda o: (o.ordinal, o.ts_created))

        return policy_list

    def _get_related_resource_ids(self, resource):
        """For given resource object, find related resources based on type"""
        resource_id_list = []
        # TODO - This could be following associations (parents, children, etc)
        return resource_id_list

    def get_active_service_access_policy_rules(self, service_name='', org_name=''):
        """Generates the set of all enabled access policies for the specified service within
        the specified Org. If the org_name is not provided, then the root ION Org will be assumed.
        """
        # TODO - extend to handle Org specific service policies at some point.
        rq = ResourceQuery()
        rq.set_filter(rq.filter_type(RT.Policy),
                      rq.filter_attribute("enabled", True))
        if service_name:
            rq.add_filter(rq.or_(rq.filter_attribute("policy_type", PolicyTypeEnum.COMMON_SERVICE_ACCESS),
                                 rq.and_(rq.filter_attribute("policy_type", [PolicyTypeEnum.SERVICE_ACCESS, PolicyTypeEnum.SERVICE_OP_PRECOND]),
                                         rq.filter_attribute("details.service_name", service_name))))
        else:
            rq.add_filter(rq.filter_attribute("policy_type", PolicyTypeEnum.COMMON_SERVICE_ACCESS))
        policy_list = self.clients.resource_registry.find_resources_ext(query=rq.get_query(), id_only=False)

        policy_list.sort(key=lambda o: (o.ordinal, o.ts_created))

        return policy_list

    def get_active_service_operation_preconditions(self, service_name='', op='', org_name=''):
        """Generates the set of all enabled precondition policies for the specified service operation
        within the specified Org. If the org_name is not provided, then the root ION Org will be assumed.
        """
        # TODO - extend to handle Org specific service policies at some point.
        if not service_name:
            raise BadRequest("The service_name argument is missing")

        rq = ResourceQuery()
        rq.set_filter(rq.filter_type(RT.Policy),
                      rq.filter_attribute("enabled", True),
                      rq.filter_attribute("policy_type", PolicyTypeEnum.SERVICE_OP_PRECOND),
                      rq.filter_attribute("details.service_name", service_name))
        if op:
            rq.add_filter(rq.filter_attribute("details.op", op))
        policy_list = self.clients.resource_registry.find_resources_ext(query=rq.get_query(), id_only=False)
        policy_list.sort(key=lambda o: (o.ordinal, o.ts_created))

        return policy_list

    def get_active_process_operation_preconditions(self, process_key='', op='', org_name=''):
        """Generates the set of all enabled precondition policies for the specified process operation
        within the specified Org. If the org_name is not provided, then the root ION Org will be assumed.
        """
        # TODO - extend to handle Org specific service policies at some point.
        if not process_key:
            raise BadRequest("The process_key argument is missing")

        rq = ResourceQuery()
        rq.set_filter(rq.filter_type(RT.Policy),
                      rq.filter_attribute("enabled", True),
                      rq.filter_attribute("policy_type", PolicyTypeEnum.PROC_OP_PRECOND),
                      rq.filter_attribute("details.process_key", process_key))
        if op:
            rq.add_filter(rq.filter_attribute("details.op", op))
        policy_list = self.clients.resource_registry.find_resources_ext(query=rq.get_query(), id_only=False)
        policy_list.sort(key=lambda o: (o.ordinal, o.ts_created))

        return policy_list

    # Local helper functions for testing policies - do not remove

    def func1_pass(self, msg, header):
        return True, ''

    def func2_deny(self,  msg, header):
        return False, 'Denied for no reason'
class UserNotificationService(BaseUserNotificationService):
    """
    A service that provides users with an API for CRUD methods for notifications.
    """
    def __init__(self, *args, **kwargs):
        self._schedule_ids = []
        BaseUserNotificationService.__init__(self, *args, **kwargs)

    def on_start(self):
        self.ION_NOTIFICATION_EMAIL_ADDRESS = CFG.get_safe('server.smtp.sender')

        # Create an event processor
        self.event_processor = EmailEventProcessor()

        # Dictionaries that maintain information asetting_up_smtp_clientbout users and their subscribed notifications
        self.user_info = {}

        # The reverse_user_info is calculated from the user_info dictionary
        self.reverse_user_info = {}

        self.event_publisher = EventPublisher(process=self)

        self.start_time = get_ion_ts()

        #------------------------------------------------------------------------------------
        # Create an event subscriber for Reload User Info events
        #------------------------------------------------------------------------------------

        def reload_user_info(event_msg, headers):
            """
            Callback method for the subscriber to ReloadUserInfoEvent
            """

            notification_id =  event_msg.notification_id
            log.debug("(UNS instance) received a ReloadNotificationEvent. The relevant notification_id is %s" % notification_id)

            try:
                self.user_info = self.load_user_info()
            except NotFound:
                log.warning("ElasticSearch has not yet loaded the user_index.")

            self.reverse_user_info =  calculate_reverse_user_info(self.user_info)

            log.debug("(UNS instance) After a reload, the user_info: %s" % self.user_info)
            log.debug("(UNS instance) The recalculated reverse_user_info: %s" % self.reverse_user_info)

        # the subscriber for the ReloadUSerInfoEvent
        self.reload_user_info_subscriber = EventSubscriber(
            event_type=OT.ReloadUserInfoEvent,
            origin='UserNotificationService',
            callback=reload_user_info
        )
        self.add_endpoint(self.reload_user_info_subscriber)

    def on_quit(self):
        """
        Handles stop/terminate.

        Cleans up subscribers spawned here, terminates any scheduled tasks to the scheduler.
        """
        for sid in self._schedule_ids:
            try:
                self.clients.scheduler.cancel_timer(sid)
            except IonException as ex:
                log.info("Ignoring exception while cancelling schedule id (%s): %s: %s", sid, ex.__class__.__name__, ex)

        super(UserNotificationService, self).on_quit()

    def set_process_batch_key(self, process_batch_key = ''):
        """
        This method allows an operator to set the process_batch_key, a string.
        Once this method is used by the operator, the UNS will start listening for timer events
        published by the scheduler with origin = process_batch_key.

        @param process_batch_key str
        """
        def process(event_msg, headers):
            self.end_time = get_ion_ts()

            # run the process_batch() method
            self.process_batch(start_time=self.start_time, end_time=self.end_time)
            self.start_time = self.end_time

        # the subscriber for the batch processing
        # To trigger the batch notification, have the scheduler create a timer with event_origin = process_batch_key
        self.batch_processing_subscriber = EventSubscriber(
            event_type=OT.TimerEvent,
            origin=process_batch_key,
            callback=process
        )
        self.add_endpoint(self.batch_processing_subscriber)

    def create_notification(self, notification=None, user_id=''):
        """
        Persists the provided NotificationRequest object for the specified Origin id.
        Associate the Notification resource with the user_id string.
        returned id is the internal id by which NotificationRequest will be identified
        in the data store.

        @param notification        NotificationRequest
        @param user_id             str
        @retval notification_id    str
        @throws BadRequest    if object passed has _id or _rev attribute
        """
        if not user_id:
            raise BadRequest("User id not provided.")

        log.debug("Create notification called for user_id: %s, and notification: %s", user_id, notification)

        #---------------------------------------------------------------------------------------------------
        # Persist Notification object as a resource if it has already not been persisted
        #---------------------------------------------------------------------------------------------------

        notification_id = None
        # if the notification has already been registered, simply use the old id
        existing_user_notifications = self.get_user_notifications(user_info_id=user_id)
        if existing_user_notifications:
            notification_id = self._notification_in_notifications(notification, existing_user_notifications)

        # since the notification has not been registered yet, register it and get the id

        temporal_bounds = TemporalBounds()
        temporal_bounds.start_datetime = get_ion_ts()
        temporal_bounds.end_datetime = ''

        if not notification_id:
            notification.temporal_bounds = temporal_bounds
            notification_id, rev = self.clients.resource_registry.create(notification)
        else:
            log.debug("Notification object has already been created in resource registry before. No new id to be generated. notification_id: %s", notification_id)
            # Read the old notification already in the resource registry
            notification = self.clients.resource_registry.read(notification_id)

            # Update the temporal bounds of the old notification resource
            notification.temporal_bounds = temporal_bounds

            # Update the notification in the resource registry
            self.clients.resource_registry.update(notification)

            log.debug("The temporal bounds for this resubscribed notification object with id: %s, is: %s", notification._id,notification.temporal_bounds)


        # Link the user and the notification with a hasNotification association
        assocs= self.clients.resource_registry.find_associations(subject=user_id,
                                                                    predicate=PRED.hasNotification,
                                                                    object=notification_id,
                                                                    id_only=True)

        if assocs:
            log.debug("Got an already existing association: %s, between user_id: %s, and notification_id: %s", assocs,user_id,notification_id)
            return notification_id
        else:
            log.debug("Creating association between user_id: %s, and notification_id: %s", user_id, notification_id )
            self.clients.resource_registry.create_association(user_id, PRED.hasNotification, notification_id)

        # read the registered notification request object because this has an _id and is more useful
        notification = self.clients.resource_registry.read(notification_id)

        #-------------------------------------------------------------------------------------------------------------------
        # Generate an event that can be picked by a notification worker so that it can update its user_info dictionary
        #-------------------------------------------------------------------------------------------------------------------
        #log.debug("(create notification) Publishing ReloadUserInfoEvent for notification_id: %s", notification_id)

        self.event_publisher.publish_event( event_type= OT.ReloadUserInfoEvent,
            origin="UserNotificationService",
            description= "A notification has been created.",
            notification_id = notification_id)

        return notification_id

    def update_notification(self, notification=None, user_id = ''):
        """Updates the provided NotificationRequest object.  Throws NotFound exception if
        an existing version of NotificationRequest is not found.  Throws Conflict if
        the provided NotificationRequest object is not based on the latest persisted
        version of the object.

        @param notification     NotificationRequest
        @throws BadRequest      if object does not have _id or _rev attribute
        @throws NotFound        object with specified id does not exist
        @throws Conflict        object not based on latest persisted object version
        """

        raise NotImplementedError("This method needs to be worked out in terms of implementation")

#        #-------------------------------------------------------------------------------------------------------------------
#        # Get the old notification
#        #-------------------------------------------------------------------------------------------------------------------
#
#        old_notification = self.clients.resource_registry.read(notification._id)
#
#        #-------------------------------------------------------------------------------------------------------------------
#        # Update the notification in the notifications dict
#        #-------------------------------------------------------------------------------------------------------------------
#
#
#        self._update_notification_in_notifications_dict(new_notification=notification,
#                                                        notifications=self.notifications)
#        #-------------------------------------------------------------------------------------------------------------------
#        # Update the notification in the registry
#        #-------------------------------------------------------------------------------------------------------------------
#        '''
#        Since one user should not be able to update the notification request resource without the knowledge of other users
#        who have subscribed to the same notification request, we do not update the resource in the resource registry
#        '''
#
##        self.clients.resource_registry.update(notification)
#
#        #-------------------------------------------------------------------------------------------------------------------
#        # reading up the notification object to make sure we have the newly registered notification request object
#        #-------------------------------------------------------------------------------------------------------------------
#
#        notification_id = notification._id
#        notification = self.clients.resource_registry.read(notification_id)
#
#        #------------------------------------------------------------------------------------
#        # Update the UserInfo object
#        #------------------------------------------------------------------------------------
#
#        user = self.update_user_info_object(user_id, notification)
#
#        #-------------------------------------------------------------------------------------------------------------------
#        # Generate an event that can be picked by notification workers so that they can update their user_info dictionary
#        #-------------------------------------------------------------------------------------------------------------------
#        log.info("(update notification) Publishing ReloadUserInfoEvent for updated notification")
#
#        self.event_publisher.publish_event( event_type= "ReloadUserInfoEvent",
#            origin="UserNotificationService",
#            description= "A notification has been updated.",
#            notification_id = notification_id
#        )

    def read_notification(self, notification_id=''):
        """Returns the NotificationRequest object for the specified notification id.
        Throws exception if id does not match any persisted NotificationRequest
        objects.

        @param notification_id    str
        @retval notification    NotificationRequest
        @throws NotFound    object with specified id does not exist
        """
        notification = self.clients.resource_registry.read(notification_id)

        return notification

    def delete_notification(self, notification_id=''):
        """For now, permanently deletes NotificationRequest object with the specified
        id. Throws exception if id does not match any persisted NotificationRequest.

        @param notification_id    str
        @throws NotFound    object with specified id does not exist
        """

        #-------------------------------------------------------------------------------------------------------------------
        # Stop the event subscriber for the notification
        #-------------------------------------------------------------------------------------------------------------------
        notification_request = self.clients.resource_registry.read(notification_id)

        #-------------------------------------------------------------------------------------------------------------------
        # Update the resource registry
        #-------------------------------------------------------------------------------------------------------------------

        notification_request.temporal_bounds.end_datetime = get_ion_ts()

        self.clients.resource_registry.update(notification_request)

        #-------------------------------------------------------------------------------------------------------------------
        # Find users who are interested in the notification and update the notification in the list maintained by the UserInfo object
        #-------------------------------------------------------------------------------------------------------------------
#        user_ids, _ = self.clients.resource_registry.find_subjects(RT.UserInfo, PRED.hasNotification, notification_id, True)
#
#        for user_id in user_ids:
#            self.update_user_info_object(user_id, notification_request)

        #-------------------------------------------------------------------------------------------------------------------
        # Generate an event that can be picked by a notification worker so that it can update its user_info dictionary
        #-------------------------------------------------------------------------------------------------------------------
        log.info("(delete notification) Publishing ReloadUserInfoEvent for notification_id: %s", notification_id)

        self.event_publisher.publish_event( event_type= OT.ReloadUserInfoEvent,
            origin="UserNotificationService",
            description= "A notification has been deleted.",
            notification_id = notification_id)

#    def delete_notification_from_user_info(self, notification_id):
#        """
#        Helper method to delete the notification from the user_info dictionary
#
#        @param notification_id str
#        """
#
#        user_ids, assocs = self.clients.resource_registry.find_subjects(object=notification_id, predicate=PRED.hasNotification, id_only=True)
#
#        for assoc in assocs:
#            self.clients.resource_registry.delete_association(assoc)
#
#        for user_id in user_ids:
#
#            value = self.user_info[user_id]
#
#            for notif in value['notifications']:
#                if notification_id == notif._id:
#                    # remove the notification
#                    value['notifications'].remove(notif)
#
#        self.reverse_user_info = calculate_reverse_user_info(self.user_info)

    def get_user_notifications(self, user_info_id=''):
        """
        Get the notification request objects that are subscribed to by the user

        @param user_info_id str

        @retval notifications list of NotificationRequest objects
        """
        notifications = []
        user_notif_req_objs, _ = self.clients.resource_registry.find_objects(
            subject=user_info_id, predicate=PRED.hasNotification, object_type=RT.NotificationRequest, id_only=False)

        log.debug("Got %s notifications, for the user: %s", len(user_notif_req_objs), user_info_id)

        for notif in user_notif_req_objs:
            # do not include notifications that have expired
            if notif.temporal_bounds.end_datetime == '':
                    notifications.append(notif)

        return notifications


    def create_worker(self, number_of_workers=1):
        """
        Creates notification workers

        @param number_of_workers int
        @retval pids list

        """

        pids = []

        for n in xrange(number_of_workers):

            process_definition = ProcessDefinition( name='notification_worker_%s' % n)

            process_definition.executable = {
                'module': 'ion.processes.data.transforms.notification_worker',
                'class':'NotificationWorker'
            }
            process_definition_id = self.clients.process_dispatcher.create_process_definition(process_definition=process_definition)

            # ------------------------------------------------------------------------------------
            # Process Spawning
            # ------------------------------------------------------------------------------------

            pid2 = self.clients.process_dispatcher.create_process(process_definition_id)

            #@todo put in a configuration
            configuration = {}
            configuration['process'] = dict({
                'name': 'notification_worker_%s' % n,
                'type':'simple',
                'queue_name': 'notification_worker_queue'
            })

            pid  = self.clients.process_dispatcher.schedule_process(
                process_definition_id,
                configuration = configuration,
                process_id=pid2
            )

            pids.append(pid)

        return pids

    def process_batch(self, start_time = '', end_time = ''):
        """
        This method is launched when an process_batch event is received. The user info dictionary maintained
        by the User Notification Service is used to query the event repository for all events for a particular
        user that have occurred in a provided time interval, and then an email is sent to the user containing
        the digest of all the events.

        @param start_time int milliseconds
        @param end_time int milliseconds
        """
        self.smtp_client = setting_up_smtp_client()

        if end_time <= start_time:
            return

        for user_id, value in self.user_info.iteritems():

            notifications = self.get_user_notifications(user_info_id=user_id)
            notifications_disabled = value['notifications_disabled']
            notifications_daily_digest = value['notifications_daily_digest']


            # Ignore users who do NOT want batch notifications or who have disabled the delivery switch
            # However, if notification preferences have not been set for the user, use the default mechanism and do not bother
            if notifications_disabled or not notifications_daily_digest:
                    continue

            events_for_message = []

            search_time = "SEARCH 'ts_created' VALUES FROM %s TO %s FROM 'events_index'" % (start_time, end_time)

            for notification in notifications:
                # If the notification request has expired, then do not use it in the search
                if notification.temporal_bounds.end_datetime:
                    continue

                event_tuples = self.container.event_repository.find_events(
                    origin=notification.origin,
                    event_type=notification.event_type,
                    start_ts=start_time,
                    end_ts=end_time)
                events = [item[2] for item in event_tuples]

                events_for_message.extend(events)

            log.debug("Found following events of interest to user, %s: %s", user_id, events_for_message)

            # send a notification email to each user using a _send_email() method
            if events_for_message:
                self.format_and_send_email(events_for_message = events_for_message,
                                            user_id = user_id,
                                            smtp_client=self.smtp_client)

        self.smtp_client.quit()


    def format_and_send_email(self, events_for_message=None, user_id=None, smtp_client=None):
        """
        Format the message for a particular user containing information about the events he is to be notified about

        @param events_for_message list
        @param user_id str
        """
        message = str(events_for_message)
        log.debug("The user, %s, will get the following events in his batch notification email: %s", user_id, message)

        msg = convert_events_to_email_message(events_for_message, self.clients.resource_registry)
        msg["Subject"] = "(SysName: " + get_sys_name() + ") ION event "
        msg["To"] = self.user_info[user_id]['user_contact'].email
        self.send_batch_email(msg, smtp_client)


    def send_batch_email(self, msg=None, smtp_client=None):
        """
        Send the email

        @param msg MIMEText object of email message
        @param smtp_client object
        """

        if msg is None: msg = {}
        for f in ["Subject", "To"]:
            if not f in msg: raise BadRequest("'%s' not in msg %s" % (f, msg))

        msg_subject = msg["Subject"]
        msg_recipient = msg["To"]

        msg['From'] = self.ION_NOTIFICATION_EMAIL_ADDRESS
        log.debug("UNS sending batch (digest) email from %s to %s",
                  self.ION_NOTIFICATION_EMAIL_ADDRESS,
                  msg_recipient)

        smtp_sender = CFG.get_safe('server.smtp.sender')

        smtp_client.sendmail(smtp_sender, [msg_recipient], msg.as_string())

    def update_user_info_object(self, user_id, new_notification):
        """
        Update the UserInfo object. If the passed in parameter, od_notification, is None, it does not need to remove the old notification

        @param user_id str
        @param new_notification NotificationRequest
        """

        #this is not necessary if notifiactions are not stored in the userinfo object
        raise NotImplementedError("This method is not necessary because Notifications are not stored in the userinfo object")

        #------------------------------------------------------------------------------------
        # read the user
        #------------------------------------------------------------------------------------

#        user = self.clients.resource_registry.read(user_id)
#
#        if not user:
#            raise BadRequest("No user with the provided user_id: %s" % user_id)
#
#        for item in user.variables:
#            if type(item) is dict and item.has_key('name') and item['name'] == 'notifications':
#                for notif in item['value']:
#                    if notif._id == new_notification._id:
#                        log.debug("came here for updating notification")
#                        notifications = item['value']
#                        notifications.remove(notif)
#                        notifications.append(new_notification)
#                break
#            else:
#                log.warning('Invalid variables attribute on UserInfo instance. UserInfo: %s', user)
#
#
#        #------------------------------------------------------------------------------------
#        # update the resource registry
#        #------------------------------------------------------------------------------------
#
#        log.debug("user.variables::: %s", user.variables)
#
#        self.clients.resource_registry.update(user)
#
#        return user


    def get_subscriptions(self, resource_id='', user_id = '', include_nonactive=False):
        """
        @param resource_id  a resource id (or other origin) that is the origin of events for notifications
        @param user_id  a UserInfo ID that owns the NotificationRequest
        @param include_nonactive  if False, filter to active NotificationRequest only
        Return all NotificationRequest resources where origin is given resource_id.
        """
        notif_reqs, _ = self.clients.resource_registry.find_resources_ext(
            restype=RT.NotificationRequest, attr_name="origin", attr_value=resource_id, id_only=False)

        log.debug("Got %s active and past NotificationRequests for resource/origin %s", len(notif_reqs), resource_id)

        if not include_nonactive:
            notif_reqs = [nr for nr in notif_reqs if nr.temporal_bounds.end_datetime == '']
            log.debug("Filtered to %s active NotificationRequests", len(notif_reqs))

        if user_id:
            # Get all NotificationRequests (ID) that are associated to given UserInfo_id
            user_notif_req_ids, _ = self.clients.resource_registry.find_objects(
                subject=user_id, predicate=PRED.hasNotification, object_type=RT.NotificationRequest, id_only=True)

            notif_reqs = [nr for nr in notif_reqs if nr._id in user_notif_req_ids]
            log.debug("Filtered to %s NotificationRequests associated to user %s", len(notif_reqs), user_id)

        return notif_reqs

    def get_subscriptions_attribute(self, resource_id='', user_id = '', include_nonactive=False):
        retval = self.get_subscriptions(resource_id=resource_id, user_id=user_id, include_nonactive=include_nonactive)
        container = ComputedListValue(value=retval)
        return container


    def _notification_in_notifications(self, notification = None, notifications = None):

        for notif in notifications:
            if notif.name == notification.name and \
            notif.origin == notification.origin and \
            notif.origin_type == notification.origin_type and \
            notif.event_type == notification.event_type:
                return notif._id
        return None

    def load_user_info(self):
        """
        Method to load the user info dictionary used by the notification workers and the UNS

        @retval user_info dict
        """
        users, _ = self.clients.resource_registry.find_resources(restype=RT.UserInfo)

        user_info = {}

        if not users:
            return {}

        for user in users:
            notifications = []
            notifications_disabled = False
            notifications_daily_digest = False

            #retrieve all the active notifications assoc to this user
            notifications = self.get_user_notifications(user_info_id=user)
            log.debug('load_user_info notifications:   %s', notifications)

            for variable in user.variables:
                if type(variable) is dict and variable.has_key('name'):

                    if variable['name'] == 'notifications_daily_digest':
                        notifications_daily_digest = variable['value']

                    if variable['name'] == 'notifications_disabled':
                        notifications_disabled = variable['value']

                else:
                    log.warning('Invalid variables attribute on UserInfo instance. UserInfo: %s', user)

            user_info[user._id] = { 'user_contact' : user.contact, 'notifications' : notifications,
                                    'notifications_daily_digest' : notifications_daily_digest, 'notifications_disabled' : notifications_disabled}

        return user_info


    # -------------------------------------------------------------------------
    #  Governance operations

    def check_subscription_policy(self, process, message, headers):

        try:
            gov_values = GovernanceHeaderValues(headers=headers, process=process, resource_id_required=False)

        except Inconsistent, ex:
            return False, ex.message

        if gov_values.op == 'delete_notification':
            return True, ''

        notification = message['notification']
        resource_id = notification.origin

        if notification.origin_type == RT.Org:
            org = self.clients.resource_registry.read(resource_id)
            if (has_org_role(gov_values.actor_roles, org.org_governance_name, [ORG_MEMBER_ROLE])):
                    return True, ''
        else:
            orgs,_ = self.clients.resource_registry.find_subjects(subject_type=RT.Org, predicate=PRED.hasResource, object=resource_id, id_only=False)
            for org in orgs:
                if (has_org_role(gov_values.actor_roles, org.org_governance_name, [ORG_MEMBER_ROLE])):
                    return True, ''

        return False, '%s(%s) has been denied since the user is not a member in any org to which the resource id %s belongs ' % (process.name, gov_values.op, resource_id)
    def test_user_role_cache(self):
        # Create a user
        id_client = IdentityManagementServiceClient()

        actor_id, valid_until, registered = id_client.signon(USER1_CERTIFICATE, True)

        # Make a request with this new user  to get it into the cache
        response = self.test_app.get(
            "/ion-service/resource_registry/find_resources?name=TestDataProduct&id_only=True&requester=" + actor_id
        )
        self.check_response_headers(response)
        self.assertIn(GATEWAY_RESPONSE, response.json["data"])

        # Check the contents of the user role cache for this user
        service_gateway_user_role_cache = self.container.proc_manager.procs_by_name["service_gateway"].user_role_cache
        self.assertEqual(service_gateway_user_role_cache.has_key(actor_id), True)

        role_header = service_gateway_user_role_cache.get(actor_id)
        self.assertIn("ION", role_header)
        self.assertEqual(len(role_header["ION"]), 1)
        self.assertIn("ORG_MEMBER", role_header["ION"])

        org_client = OrgManagementServiceClient()

        ion_org = org_client.find_org()
        manager_role = org_client.find_org_role_by_name(org_id=ion_org._id, role_name="ORG_MANAGER")

        org_client.grant_role(org_id=ion_org._id, actor_id=actor_id, role_name="ORG_MANAGER")

        # Just allow some time for event processing on slower platforms
        gevent.sleep(2)

        # The user should be evicted from the cache due to a change in roles
        self.assertEqual(service_gateway_user_role_cache.has_key(actor_id), False)

        # Do it again to check for new roles
        response = self.test_app.get(
            "/ion-service/resource_registry/find_resources?name=TestDataProduct&id_only=True&requester=" + actor_id
        )
        self.check_response_headers(response)
        self.assertIn(GATEWAY_RESPONSE, response.json["data"])

        # Check the contents of the user role cache for this user
        self.assertEqual(service_gateway_user_role_cache.has_key(actor_id), True)

        role_header = service_gateway_user_role_cache.get(actor_id)
        self.assertIn("ION", role_header)
        self.assertEqual(len(role_header["ION"]), 2)
        self.assertIn("ORG_MEMBER", role_header["ION"])
        self.assertIn("ORG_MANAGER", role_header["ION"])

        # Now flush the user_role_cache and make sure it was flushed
        event_publisher = EventPublisher()
        event_publisher.publish_event(event_type=OT.UserRoleCacheResetEvent)

        # Just allow some time for event processing on slower platforms
        gevent.sleep(2)

        self.assertEqual(service_gateway_user_role_cache.has_key(actor_id), False)
        self.assertEqual(service_gateway_user_role_cache.size(), 0)

        # Change the role once again and see if it is there again
        org_client.revoke_role(org_id=ion_org._id, actor_id=actor_id, role_name="ORG_MANAGER")

        # Just allow some time for event processing on slower platforms
        gevent.sleep(2)

        # The user should still not be there
        self.assertEqual(service_gateway_user_role_cache.has_key(actor_id), False)

        # Do it again to check for new roles
        response = self.test_app.get(
            "/ion-service/resource_registry/find_resources?name=TestDataProduct&id_only=True&requester=" + actor_id
        )
        self.check_response_headers(response)
        self.assertIn(GATEWAY_RESPONSE, response.json["data"])

        # Check the contents of the user role cache for this user
        self.assertEqual(service_gateway_user_role_cache.has_key(actor_id), True)

        role_header = service_gateway_user_role_cache.get(actor_id)
        self.assertIn("ION", role_header)
        self.assertEqual(len(role_header["ION"]), 1)
        self.assertIn("ORG_MEMBER", role_header["ION"])

        id_client.delete_actor_identity(actor_id)
 def perform_action(self, predicate=None, action=None):
     userid = None  # get from context
     event_pub = EventPublisher(process=self)
     event_pub.publish_event(event_type=OT.ContainerManagementRequest, origin=userid, predicate=predicate, action=action)
Exemplo n.º 11
0
 def on_init(self):
     self.rr = self.clients.resource_registry
     self.event_pub = EventPublisher(process=self)
     self.negotiation_handler = Negotiation(self, negotiation_rules, self.event_pub)
     self.root_org_id = None
Exemplo n.º 12
0
class OrgManagementService(BaseOrgManagementService):
    """
    Services to define and administer an Org (organization, facility), to enroll/remove members and to provide
    access to the resources of an Org to enrolled or affiliated entities (identities). Contains contract
    and commitment repository
    """
    def on_init(self):
        self.rr = self.clients.resource_registry
        self.event_pub = EventPublisher(process=self)
        self.negotiation_handler = Negotiation(self, negotiation_rules, self.event_pub)
        self.root_org_id = None

    def _get_root_org_name(self):
        if self.container is None or self.container.governance_controller is None:
            return CFG.get_safe("system.root_org", "ION")

        return self.container.governance_controller.system_root_org_name

    def _get_root_org_id(self):
        if not self.root_org_id:
            root_org = self.find_org()
            self.root_org_id = root_org._id
        return self.root_org_id

    def _validate_user_role(self, arg_name, role_name, org_id):
        """
        Check that the given argument is a resource id, by retrieving the resource from the
        resource registry. Additionally checks type and returns the result object
        """
        if not role_name:
            raise BadRequest("The argument '%s' is missing" % arg_name)
        if not org_id:
            raise BadRequest("The argument 'org_id' is missing")
        user_role = self._find_org_role(org_id, role_name)
        return user_role

    # -------------------------------------------------------------------------
    # Org management (CRUD)

    def create_org(self, org=None):
        """Creates an Org based on the provided object. The id string returned
        is the internal id by which Org will be identified in the data store.
        """
        # Only allow one root ION Org in the system
        self._validate_resource_obj("org", org, RT.Org, checks="noid,name,unique")

        # If this governance identifier is not set, then set to a safe version of the org name.
        if not org.org_governance_name:
            org.org_governance_name = create_basic_identifier(org.name)
        if not is_basic_identifier(org.org_governance_name):
            raise BadRequest("The Org org_governance_name '%s' contains invalid characters" % org.org_governance_name)

        org_id, _ = self.rr.create(org)

        # Instantiate a Directory for this Org
        directory = Directory(orgname=org.name)

        # Instantiate initial set of User Roles for this Org
        self._create_org_roles(org_id)

        return org_id

    def _create_org_roles(self, org_id):
        # Instantiate initial set of User Roles for this Org
        manager_role = IonObject(RT.UserRole, name="MODERATOR", governance_name=MODERATOR_ROLE,
                                 description="Manage organization members, resources and roles")
        self.add_org_role(org_id, manager_role)

        operator_role = IonObject(RT.UserRole, name="OPERATOR", governance_name=OPERATOR_ROLE,
                                description="Modify and control organization resources")
        self.add_org_role(org_id, operator_role)

        member_role = IonObject(RT.UserRole, name="MEMBER", governance_name=MEMBER_ROLE,
                                description="Access organization resources")
        self.add_org_role(org_id, member_role)

    def update_org(self, org=None):
        """Updates the Org based on provided object.
        """
        old_org = self._validate_resource_obj("org", org, RT.Org, checks="id")
        if org.org_governance_name != old_org.org_governance_name:
            raise BadRequest("Cannot update Org org_governance_name")

        self.rr.update(org)

    def read_org(self, org_id=""):
        """Returns the Org object for the specified id.
        Throws exception if id does not match any persisted Org objects.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)

        return org_obj

    def delete_org(self, org_id=""):
        """Permanently deletes Org object with the specified
        id. Throws exception if id does not match any persisted Org object.
        """
        self._validate_resource_id("org_id", org_id, RT.Org)

        self.rr.delete(org_id)

    def find_org(self, name=""):
        """Finds an Org object with the specified name. Defaults to the
        root ION object. Throws a NotFound exception if the object does not exist.
        """

        # Default to the root ION Org if not specified
        if not name:
            name = self._get_root_org_name()

        res_list, _ = self.rr.find_resources(restype=RT.Org, name=name, id_only=False)
        if not res_list:
            raise NotFound("The Org with name %s does not exist" % name)
        return res_list[0]

    # -------------------------------------------------------------------------
    # Org roles

    def add_org_role(self, org_id="", user_role=None):
        """Adds a UserRole to an Org, if the role by the specified
       name does not exist.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)
        self._validate_resource_obj("user_role", user_role, RT.UserRole, checks="noid,name")
        if not is_basic_identifier(user_role.governance_name):
            raise BadRequest("Invalid role governance_name")

        user_role.org_governance_name = org_obj.org_governance_name

        try:
            self._find_org_role(org_id, user_role.governance_name)
            raise BadRequest("Role '%s' is already associated with this Org" % user_role.governance_name)
        except NotFound:
            pass

        user_role_id, _ = self.rr.create(user_role)

        self.rr.create_association(org_obj, PRED.hasRole, user_role_id)

        return user_role_id

    def remove_org_role(self, org_id="", role_name="", force_removal=False):
        """Removes a UserRole from an Org. The UserRole will not be removed if there are
       users associated with the UserRole unless the force_removal parameter is set to True
        """
        self._validate_resource_id("org_id", org_id, RT.Org)
        user_role = self._validate_user_role("role_name", role_name, org_id)

        if not force_removal:
            alist, _ = self.rr.find_subjects(RT.ActorIdentity, PRED.hasRole, user_role)
            if alist:
                raise BadRequest("UserRole %s still in use and cannot be removed" % user_role.name)

        self.rr.delete(user_role._id)

    def find_org_role_by_name(self, org_id="", role_name=""):
        """Returns the UserRole object for the specified name in the Org.
        """
        self._validate_resource_id("org_id", org_id, RT.Org)
        user_role = self._find_org_role(org_id, role_name)

        return user_role

    def _find_org_role(self, org_id="", role_name=""):
        if not org_id:
            raise BadRequest("The org_id argument is missing")
        if not role_name:
            raise BadRequest("The role_name argument is missing")

        # Iterating (vs. query) is just fine, because the number of org roles is sufficiently small
        org_roles = self._list_org_roles(org_id)
        for role in org_roles:
            if role.governance_name == role_name:
                return role

        raise NotFound("Role %s not found in Org id=%s" % (role_name, org_id))

    def list_org_roles(self, org_id=""):
        """Returns a list of roles available in an Org. Will throw a not NotFound exception
        if none of the specified ids do not exist.
        """
        self._validate_resource_id("org_id", org_id, RT.Org)
        return self._list_org_roles(org_id)

    def _list_org_roles(self, org_id=""):
        if not org_id:
            raise BadRequest("Illegal org_id")

        role_list, _ = self.rr.find_objects(org_id, PRED.hasRole, RT.UserRole, id_only=False)
        return role_list

    # -------------------------------------------------------------------------
    # Negotiations

    def negotiate(self, sap=None):
        """A generic operation for negotiating actions with an Org, such as for enrollment, role request
        or to acquire a resource managed by the Org. The ServiceAgreementProposal object is used to
        specify conditions of the proposal as well as counter proposals and the Org will create a
        Negotiation resource to track the history and status of the negotiation.
        """

        if sap is None or (sap.type_ != OT.ServiceAgreementProposal and not issubtype(sap.type_, OT.ServiceAgreementProposal)):
            raise BadRequest("The sap argument must be a valid ServiceAgreementProposal object")

        if sap.proposal_status == ProposalStatusEnum.INITIAL:
            neg_id = self.negotiation_handler.create_negotiation(sap)

            org = self.read_org(org_id=sap.provider)

            # Publish an event indicating an Negotiation has been initiated
            self.event_pub.publish_event(event_type=OT.OrgNegotiationInitiatedEvent, origin=org._id, origin_type="Org",
                                         description=sap.description, org_name=org.name,
                                         negotiation_id=neg_id, sub_type=sap.type_)

            # Synchronize the internal reference for later use
            sap.negotiation_id = neg_id

        # Get the most recent version of the Negotiation resource
        negotiation = self.negotiation_handler.read_negotiation(sap)

        # Update the Negotiation object with the latest SAP
        neg_id = self.negotiation_handler.update_negotiation(sap)

        # Get the most recent version of the Negotiation resource
        negotiation = self.rr.read(neg_id)

        # hardcoding some rules at the moment - could be replaced by a Rules Engine
        if sap.type_ == OT.AcquireResourceExclusiveProposal:

            if self.is_resource_acquired_exclusively(None, sap.resource_id):
                # Automatically accept the proposal for exclusive access if it is not already acquired exclusively
                provider_accept_sap = Negotiation.create_counter_proposal(negotiation, ProposalStatusEnum.REJECTED, ProposalOriginatorEnum.PROVIDER)

                rejection_reason = "The resource has already been acquired exclusively"

                # Update the Negotiation object with the latest SAP
                neg_id = self.negotiation_handler.update_negotiation(provider_accept_sap, rejection_reason)

                # Get the most recent version of the Negotiation resource
                negotiation = self.rr.read(neg_id)

            else:
                # Automatically reject the proposal if the expiration request is greater than 12 hours from now or 0
                cur_time = int(get_ion_ts())
                expiration = int(cur_time +  ( 12 * 60 * 60 * 1000 )) # 12 hours from now
                if int(sap.expiration) == 0 or int(sap.expiration) > expiration:
                    # Automatically accept the proposal for exclusive access if it is not already acquired exclusively
                    provider_accept_sap = Negotiation.create_counter_proposal(negotiation, ProposalStatusEnum.REJECTED,
                                                                              ProposalOriginatorEnum.PROVIDER)

                    rejection_reason = "A proposal to acquire a resource exclusively must be more than 0 and be less than 12 hours."

                    # Update the Negotiation object with the latest SAP
                    neg_id = self.negotiation_handler.update_negotiation(provider_accept_sap, rejection_reason)

                    # Get the most recent version of the Negotiation resource
                    negotiation = self.rr.read(neg_id)

                else:
                    # Automatically accept the proposal for exclusive access if it is not already acquired exclusively
                    provider_accept_sap = Negotiation.create_counter_proposal(negotiation, ProposalStatusEnum.ACCEPTED,
                                                                              ProposalOriginatorEnum.PROVIDER)

                    # Update the Negotiation object with the latest SAP
                    neg_id = self.negotiation_handler.update_negotiation(provider_accept_sap)

                    # Get the most recent version of the Negotiation resource
                    negotiation = self.rr.read(neg_id)

        # Check to see if the rules allow for auto acceptance of the negotiations -
        # where the second party is assumed to accept if the
        # first party accepts.
        if negotiation_rules[sap.type_]["auto_accept"]:
            # Automatically accept for the consumer if the Org Manager as provider accepts the proposal
            latest_sap = negotiation.proposals[-1]

            if latest_sap.proposal_status == ProposalStatusEnum.ACCEPTED and latest_sap.originator == ProposalOriginatorEnum.PROVIDER:
                consumer_accept_sap = Negotiation.create_counter_proposal(negotiation, ProposalStatusEnum.ACCEPTED)

                # Update the Negotiation object with the latest SAP
                neg_id = self.negotiation_handler.update_negotiation(consumer_accept_sap)

                # Get the most recent version of the Negotiation resource
                negotiation = self.rr.read(neg_id)

            elif latest_sap.proposal_status == ProposalStatusEnum.ACCEPTED and latest_sap.originator == ProposalOriginatorEnum.CONSUMER:
                provider_accept_sap = Negotiation.create_counter_proposal(negotiation, ProposalStatusEnum.ACCEPTED, ProposalOriginatorEnum.PROVIDER)

                # Update the Negotiation object with the latest SAP
                neg_id = self.negotiation_handler.update_negotiation(provider_accept_sap)

                # Get the most recent version of the Negotiation resource
                negotiation = self.rr.read(neg_id)

        # Return the latest proposal
        return negotiation.proposals[-1]

    def find_org_negotiations(self, org_id="", proposal_type="", negotiation_status=-1):
        """Returns a list of negotiations for an Org. An optional proposal_type can be supplied
        or else all proposals will be returned. An optional negotiation_status can be supplied
        or else all proposals will be returned.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)

        neg_list, _ = self.rr.find_objects(org_id, PRED.hasNegotiation)

        if proposal_type:
            neg_list = [neg for neg in neg_list if neg.proposals[0].type_ == proposal_type]
        if negotiation_status > -1:
            neg_list = [neg for neg in neg_list if neg.negotiation_status == negotiation_status]

        return neg_list

    def find_org_closed_negotiations(self, org_id="", proposal_type=""):
        """Returns a list of closed negotiations for an Org - those which are Accepted or Rejected.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)

        neg_list, _ = self.rr.find_objects(org_id, PRED.hasNegotiation)

        if proposal_type:
            neg_list = [neg for neg in neg_list if neg.proposals[0].type_ == proposal_type]

        neg_list = [neg for neg in neg_list if neg.negotiation_status != NegotiationStatusEnum.OPEN]

        return neg_list

    def find_user_negotiations(self, actor_id="", org_id="", proposal_type="", negotiation_status=-1):
        """Returns a list of negotiations for a specified Actor. All negotiations for all Orgs will be returned
        unless an org_id is specified. An optional proposal_type can be supplied
        or else all proposals will be returned. An optional negotiation_status can be provided
        or else all proposals will be returned.
        """
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org, optional=True)

        neg_list, _ = self.rr.find_objects(actor_obj, PRED.hasNegotiation)

        if org_id:
            neg_list = [neg for neg in neg_list if neg.proposals[0].provider == org_id]

        if proposal_type:
            neg_list = [neg for neg in neg_list if neg.proposals[0].type_ == proposal_type]
        if negotiation_status > -1:
            neg_list = [neg for neg in neg_list if neg.negotiation_status == negotiation_status]

        return neg_list

    # -------------------------------------------------------------------------
    # Member management

    def enroll_member(self, org_id="", actor_id=""):
        """Enrolls an actor into an Org so that they may find and negotiate to use
        resources of the Org. Membership in the ION Org is implied by registration
        with the system, so a membership association to the ION Org is not maintained.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)

        if org_obj.name == self._get_root_org_name():
            raise BadRequest("A request to enroll in the root ION Org is not allowed")

        self.rr.create_association(org_obj, PRED.hasMember, actor_obj)

        member_role = self.find_org_role_by_name(org_id, MEMBER_ROLE)
        self._add_role_association(org_obj, actor_obj, member_role)

        self.event_pub.publish_event(event_type=OT.OrgMembershipGrantedEvent, origin=org_id, origin_type="Org",
                                     description="The member has enrolled in the Org",
                                     actor_id=actor_id, org_name=org_obj.name)

    def cancel_member_enrollment(self, org_id="", actor_id=""):
        """Cancels the membership of a specific actor actor within the specified Org.
        Once canceled, the actor will no longer have access to the resource of that Org.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)

        if org_obj.name == self._get_root_org_name():
            raise BadRequest("A request to cancel enrollment in the root ION Org is not allowed")

        # First remove all associations to any roles
        role_list = self.list_actor_roles(actor_id, org_id)
        for user_role in role_list:
            self._delete_role_association(org_obj, actor_obj, user_role)

        # Finally remove the association to the Org
        aid = self.rr.get_association(org_obj, PRED.hasMember, actor_obj)
        if not aid:
            raise NotFound("The membership association between the specified actor and Org is not found")

        self.rr.delete_association(aid)

        self.event_pub.publish_event(event_type=OT.OrgMembershipCancelledEvent, origin=org_id, origin_type="Org",
                                     description="The member has cancelled enrollment in the Org",
                                     actor_id=actor_id, org_name=org_obj.name)

    def is_registered(self, actor_id=""):
        """Returns True if the specified actor_id is registered with the ION system; otherwise False.
        """
        if not actor_id:
            raise BadRequest("The actor_id argument is missing")
        try:
            # If this ID happens to be another resource, just return false
            user = self.rr.read(actor_id)
            if user.type_ == RT.ActorIdentity and user.lcstate != LCS.DELETED:
                return True
        except NotFound:
            pass

        return False

    def is_enrolled(self, org_id="", actor_id=""):
        """Returns True if the specified actor_id is enrolled in the Org and False if not.
        Throws a NotFound exception if neither id is found.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)

        return self._is_enrolled(org_id, actor_id)

    def _is_enrolled(self, org_id="", actor_id=""):
        # Membership into the Root ION Org is implied as part of registration
        if org_id == self._get_root_org_id():
            return True

        try:
            self.rr.get_association(org_id, PRED.hasMember, actor_id)
        except NotFound:
            return False

        return True

    def list_enrolled_actors(self, org_id=""):
        """Returns a list of users enrolled in an Org. Will throw a not NotFound exception
        if none of the specified ids do not exist.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)

        # Membership into the Root ION Org is implied as part of registration
        if org_obj.name == self._get_root_org_name():
            user_list, _ = self.rr.find_resources(RT.ActorIdentity)
        else:
            user_list, _ = self.rr.find_objects(org_obj, PRED.hasMember, RT.ActorIdentity)

        return user_list

    def list_orgs_for_actor(self, actor_id=""):
        """Returns a list of Orgs that the actor is enrolled in. Will throw a not NotFound exception
        if none of the specified ids do not exist.
        """
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)

        org_list, _ = self.rr.find_subjects(RT.Org, PRED.hasMember, actor_obj)

        # Membership into the Root ION Org is implied as part of registration
        ion_org = self.find_org()
        org_list.append(ion_org)

        return org_list

    # -------------------------------------------------------------------------
    # Org role management

    def grant_role(self, org_id="", actor_id="", role_name="", scope=None):
        """Grants a defined role within an organization to a specific actor. A role of Member is
        automatically implied with successful enrollment. Will throw a not NotFound exception
        if none of the specified ids or role_name does not exist.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)
        user_role = self._validate_user_role("role_name", role_name, org_id)

        if not self._is_enrolled(org_id, actor_id):
            raise BadRequest("The actor is not a member of the specified Org (%s)" % org_obj.name)

        self._add_role_association(org_obj, actor_obj, user_role)

    def _add_role_association(self, org, actor, user_role):
        self.rr.create_association(actor, PRED.hasRole, user_role)

        self.event_pub.publish_event(event_type=OT.UserRoleGrantedEvent, origin=org._id, origin_type="Org",
                                     sub_type=user_role.governance_name,
                                     description="Granted the %s role" % user_role.name,
                                     actor_id=actor._id, role_name=user_role.governance_name, org_name=org.name)

    def revoke_role(self, org_id="", actor_id="", role_name=""):
        """Revokes a defined Role within an organization to a specific actor.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)
        user_role = self._validate_user_role("role_name", role_name, org_id)

        self._delete_role_association(org_obj, actor_obj, user_role)

    def _delete_role_association(self, org, actor, user_role):
        aid = self.rr.get_association(actor, PRED.hasRole, user_role)
        if not aid:
            raise NotFound("ActorIdentity %s to UserRole %s association not found" % (actor._id, user_role._id))

        self.rr.delete_association(aid)

        self.event_pub.publish_event(event_type=OT.UserRoleRevokedEvent, origin=org._id, origin_type="Org",
                                     sub_type=user_role.governance_name,
                                     description="Revoked the %s role" % user_role.name,
                                     actor_id=actor._id, role_name=user_role.governance_name, org_name=org.name)

    def has_role(self, org_id="", actor_id="", role_name=""):
        """Returns True if the specified actor_id has the specified role_name in the Org and False if not.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)
        if not role_name:
            raise BadRequest("Invalid argument role_name")

        if org_id == self._get_root_org_id() and role_name == MEMBER_ROLE:
            return True

        role_list, _ = self.rr.find_objects(actor_id, PRED.hasRole, RT.UserRole, id_only=False)
        for role in role_list:
            if role.governance_name == role_name and role.org_governance_name == org_obj.org_governance_name:
                return True

        return False

    def list_actor_roles(self, actor_id="", org_id=""):
        """Returns a list of User Roles for a specific actor in an Org.
        """
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org, optional=True)

        role_list, _ = self.rr.find_objects(actor_id, PRED.hasRole, RT.UserRole, id_only=False)

        if org_id:
            role_list = [r for r in role_list if r.org_governance_name == org_obj.org_governance_name]

        if not org_id or org_id == self._get_root_org_id():
            # Because a user is automatically enrolled with the ION Org then the membership role
            # is implied - so add it to the list
            member_role = self._find_org_role(self._get_root_org_id(), MEMBER_ROLE)
            role_list.append(member_role)

        return role_list

    # -------------------------------------------------------------------------
    # Resource sharing in Org

    def share_resource(self, org_id="", resource_id=""):
        """Share a resource with the specified Org. Once shared, the resource will be added to a directory
        of available resources within the Org.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)
        resource_obj = self._validate_resource_id("resource_id", resource_id)

        self.rr.create_association(org_obj, PRED.hasResource, resource_obj)

        self.event_pub.publish_event(event_type=OT.ResourceSharedEvent, origin=org_obj._id, origin_type="Org",
                                     sub_type=resource_obj.type_,
                                     description="The resource has been shared in the Org",
                                     resource_id=resource_id, org_name=org_obj.name )

    def unshare_resource(self, org_id="", resource_id=""):
        """Unshare a resource with the specified Org. Once unshared, the resource will be
        removed from the directory of available resources within the Org.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)
        resource_obj = self._validate_resource_id("resource_id", resource_id)

        aid = self.rr.get_association(org_obj, PRED.hasResource, resource_obj)
        if not aid:
            raise NotFound("Association between Resource and Org not found")

        self.rr.delete_association(aid)

        self.event_pub.publish_event(event_type=OT.ResourceUnsharedEvent, origin=org_obj._id, origin_type="Org",
                                     sub_type=resource_obj.type_,
                                     description="The resource has been unshared in the Org",
                                     resource_id=resource_id, org_name=org_obj.name )

    def is_resource_shared(self, org_id="", resource_id=""):
        """Returns True if the resource has been shared in the specified org_id; otherwise False is returned.
        """
        self._validate_resource_id("org_id", org_id, RT.Org)
        self._validate_resource_id("resource_id", resource_id)

        org_ids, _ = self.rr.find_subjects(RT.Org, PRED.hasResource, resource_id, id_only=True)
        return org_id in org_ids

    def list_shared_resources(self, org_id=''):
        self._validate_resource_id("org_id", org_id, RT.Org)

        if org_id == self._get_root_org_id():
            # All resources - reject for security reasons
            raise BadRequest("Cannot enumerate resources for root Org")

        res_objs, _ = self.rr.find_objects(org_id, PRED.hasResource, id_only=False)
        return res_objs

    def list_orgs_for_resource(self, resource_id=''):
        self._validate_resource_id("resource_id", resource_id)

        org_objs, _ = self.rr.find_subjects(RT.Org, PRED.hasResource, resource_id, id_only=False)
        root_org = self.find_org()
        org_objs.append(root_org)

        return org_objs

    # -------------------------------------------------------------------------
    # Resource commitments

    def acquire_resource(self, sap=None):
        """Creates a Commitment for the specified resource for a specified user within the
        specified Org as defined in the proposal. Once shared, the resource is committed to the user.
        """
        if not sap:
            raise BadRequest("The sap argument is missing")

        if sap.type_ == OT.AcquireResourceExclusiveProposal:
            exclusive = True
        else:
            exclusive = False

        commitment_id = self.create_resource_commitment(sap.provider, sap.consumer, sap.resource_id,
                                                        exclusive, int(sap.expiration))

        # Create association between the Commitment and the Negotiation objects
        self.rr.create_association(sap.negotiation_id, PRED.hasContract, commitment_id)

        return commitment_id

    def create_resource_commitment(self, org_id="", actor_id="", resource_id="", exclusive=False, expiration=0):
        """Creates a Commitment for the specified resource for a specified actor within
        the specified Org. Once shared, the resource is committed to the actor.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org, optional=True)
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)
        resource_obj = self._validate_resource_id("resource_id", resource_id)

        if org_id:
            # Check that resource is shared in Org?
            pass

        res_commitment = IonObject(OT.ResourceCommitment, resource_id=resource_id, exclusive=exclusive)

        commitment = IonObject(RT.Commitment, name="", provider=org_id, consumer=actor_id, commitment=res_commitment,
                               description="Resource Commitment", expiration=str(expiration))

        commitment._id, commitment._rev = self.rr.create(commitment)

        # Creating associations to all related objects
        self.rr.create_association(actor_id, PRED.hasCommitment, commitment._id)
        self.rr.create_association(commitment._id, PRED.hasTarget, resource_id)

        if org_id:
            self.rr.create_association(org_id, PRED.hasCommitment, commitment._id)

            self.event_pub.publish_event(event_type=OT.ResourceCommitmentCreatedEvent,
                                         origin=org_id, origin_type="Org", sub_type=resource_obj.type_,
                                         description="The resource has been committed by the Org",
                                         resource_id=resource_id, org_name=org_obj.name,
                                         commitment_id=commitment._id, commitment_type=commitment.commitment.type_)

        return commitment._id

    def release_commitment(self, commitment_id=""):
        """Release the commitment that was created for resources.
        The commitment is retained in DELETED state for historic records.
        """
        commitment_obj = self._validate_resource_id("commitment_id", commitment_id, RT.Commitment)

        self.rr.lcs_delete(commitment_id)

        self.event_pub.publish_event(event_type=OT.ResourceCommitmentReleasedEvent,
                                     origin=commitment_obj.provider, origin_type="Org", sub_type="",
                                     description="The resource has been uncommitted by the Org",
                                     resource_id=commitment_obj.commitment.resource_id,
                                     commitment_id=commitment_id, commitment_type=commitment_obj.commitment.type_)

    def find_commitments(self, org_id='', resource_id='', actor_id='', exclusive=False, include_expired=False):
        """Returns all commitments in specified org and optionally a given actor and/or optionally a given resource.
        If exclusive == True, only return exclusive commitments.
        """
        self._validate_resource_id("org_id", org_id, RT.Org, optional=True)
        self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity, optional=True)
        if not org_id and not resource_id and not actor_id:
            raise BadRequest("Must restrict search for commitments")

        if resource_id:
            com_objs, _ = self.rr.find_subjects(RT.Commitment, PRED.hasTarget, resource_id, id_only=False)
            if actor_id:
                com_objs = [c for c in com_objs if c.consumer == actor_id]
            if org_id:
                com_objs = [c for c in com_objs if c.provider == org_id]
        elif actor_id:
            com_objs, _ = self.rr.find_objects(actor_id, PRED.hasCommitment, RT.Commitment, id_only=False)
            if org_id:
                com_objs = [c for c in com_objs if c.provider == org_id]
        else:
            com_objs, _ = self.rr.find_objects(org_id, PRED.hasCommitment, RT.Commitment, id_only=False)

        if exclusive:
            com_objs = [c for c in com_objs if c.commitment.type_ == OT.ResourceCommitment and c.commitment.exclusive]
        else:
            com_objs = [c for c in com_objs if c.commitment.type_ != OT.ResourceCommitment or (
                        c.commitment.type_ == OT.ResourceCommitment and not c.commitment.exclusive)]
        if not include_expired:
            cur_time = get_ion_ts_millis()
            com_objs = [c for c in com_objs if int(c.expiration) == 0 or cur_time < int(c.expiration)]

        return com_objs

    def is_resource_acquired(self, actor_id="", resource_id=""):
        """Returns True if the specified resource_id has been acquired. The actor_id
        is optional, as the operation can return True if the resource is acquired by
        any actor or specifically by the specified actor_id, otherwise False is returned.
        """
        return self._is_resource_acquired(actor_id, resource_id, exclusive=False)

    def is_resource_acquired_exclusively(self, actor_id="", resource_id=""):
        """Returns True if the specified resource_id has been acquired exclusively.
        The actor_id is optional, as the operation can return True if the resource
        is acquired exclusively by any actor or specifically by the specified
        actor_id, otherwise False is returned.
        """
        return self._is_resource_acquired(actor_id, resource_id, exclusive=True)

    def _is_resource_acquired(self, actor_id="", resource_id="", exclusive=False):
        if not resource_id:
            raise BadRequest("The resource_id argument is missing")

        try:
            com_objs = self.find_commitments(resource_id=resource_id, actor_id=actor_id, exclusive=exclusive)
            return bool(com_objs)

        except Exception as ex:
            log.exception("Error checking acquired status, actor_id=%s, resource_id=%s" % (actor_id, resource_id))

        return False

    def find_acquired_resources(self, org_id='', actor_id='', exclusive=False, include_expired=False):
        if not org_id and not actor_id:
            raise BadRequest("Must provide org_id or actor_id")

        com_objs = self.find_commitments(org_id=org_id, actor_id=actor_id,
                                         exclusive=exclusive, include_expired=include_expired)

        res_ids = {c.commitment.resource_id for c in com_objs if c.commitment.type_ == OT.ResourceCommitment}
        res_objs = self.rr.read_mult(list(res_ids))

        return res_objs

    # -------------------------------------------------------------------------
    # Org containers

    def is_in_org(self, container):
        container_list, _ = self.rr.find_subjects(RT.Org, PRED.hasResource, container)
        return bool(container_list)

    def find_org_containers(self, org_id=""):
        """Returns a list of containers associated with an Org. Will throw a not NotFound exception
        if the specified id does not exist.
        TODO: Fix inefficient implementation with index
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)

        # Containers in the Root ION Org are implied
        if org_obj.org_governance_name == self._get_root_org_name():
            container_list, _ = self.rr.find_resources(RT.CapabilityContainer)
            container_list[:] = [container for container in container_list if not self.is_in_org(container)]
        else:
            container_list, _ = self.rr.find_objects(org_obj, PRED.hasResource, RT.CapabilityContainer)

        return container_list

    def affiliate_org(self, org_id="", affiliate_org_id=""):
        """Creates an association between multiple Orgs as an affiliation
        so that they may coordinate activities between them.
        Throws a NotFound exception if neither id is found.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)
        affiliate_org_obj = self._validate_resource_id("affiliate_org_id", affiliate_org_id, RT.Org)

        aid = self.rr.create_association(org_obj, PRED.hasAffiliation, affiliate_org_obj)
        if not aid:
            return False

    def unaffiliate_org(self, org_id="", affiliate_org_id=""):
        """Removes an association between multiple Orgs as an affiliation.
        Throws a NotFound exception if neither id is found.
        """
        org_obj = self._validate_resource_id("org_id", org_id, RT.Org)
        affiliate_org_obj = self._validate_resource_id("affiliate_org_id", affiliate_org_id, RT.Org)

        aid = self.rr.get_association(org_obj, PRED.hasAffiliation, affiliate_org_obj)
        if not aid:
            raise NotFound("The affiliation association between the specified Orgs is not found")

        self.rr.delete_association(aid)

    # Local helper functions are below - do not remove them

    def is_enroll_negotiation_open(self, org_id, actor_id):
        try:
            neg_list = self.find_user_negotiations(actor_id, org_id, proposal_type=OT.EnrollmentProposal,
                                                   negotiation_status=NegotiationStatusEnum.OPEN )

            if neg_list:
                return True

        except Exception as ex:
            log.exception("org_id:%s and actor_id:%s" % (org_id, actor_id))

        return False
Exemplo n.º 13
0
    def on_init(self):
        self.rr = self.clients.resource_registry
        #self.authentication = Authentication()

        self.event_pub = EventPublisher(process=self)
Exemplo n.º 14
0
class IdentityManagementService(BaseIdentityManagementService):
    """
    Stores identities of users and resources, including bindings of internal
    identities to external identities. Also stores metadata such as a user profile.
    """

    event_pub = None

    def on_init(self):
        self.rr = self.clients.resource_registry
        #self.authentication = Authentication()

        self.event_pub = EventPublisher(process=self)

    def create_actor_identity(self, actor_identity=None):
        self._validate_resource_obj("actor_identity", actor_identity, RT.ActorIdentity, checks="noid,name")
        if actor_identity.credentials:
            raise BadRequest("Cannot create actor with credentials")
        if actor_identity.details and actor_identity.details.type_ == OT.IdentityDetails:
            actor_identity.details = None
        actor_identity.passwd_reset_token = None

        actor_id, _ = self.rr.create(actor_identity)

        return actor_id

    def update_actor_identity(self, actor_identity=None):
        old_actor = self._validate_resource_obj("actor_identity", actor_identity, RT.ActorIdentity, checks="id,name")

        # Prevent security risk because contained credentials may be manipulated
        actor_identity.credentials = old_actor.credentials

        self.rr.update(actor_identity)

    def read_actor_identity(self, actor_id=''):
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)

        return actor_obj

    def delete_actor_identity(self, actor_id=''):
        self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)

        self.rr.delete(actor_id)

    def find_actor_identity_by_name(self, name=''):
        """Return the ActorIdentity object whose name attribute matches the passed value.
        """
        objects, _ = self.rr.find_resources(RT.ActorIdentity, None, name, id_only=False)
        if not objects:
            raise NotFound("ActorIdentity with name %s does not exist" % name)
        if len(objects) > 1:
            raise Inconsistent("Multiple ActorIdentity objects with name %s exist" % name)
        return objects[0]

    # -------------------------------------------------------------------------
    # Credentials handling

    def register_credentials(self, actor_id='', credentials=None):
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)
        self._validate_arg_obj("credentials", credentials, OT.Credentials)

        actor_obj.credentials.append(credentials)

        if credentials.username:
            actor_obj.alt_ids.append("UNAME:" + credentials.username)

        # Lower level RR call to avoid credentials clearing
        self.rr.update(actor_obj)

    def unregister_credentials(self, actor_id='', credentials_name=''):
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)
        if not credentials_name:
            raise BadRequest("Invalid credentials_name")
        found_cred = -1
        for i, cred in enumerate(actor_obj.credentials):
            if cred.username == credentials_name:
                found_cred = i
                break
        if found_cred != -1:
            del actor_obj.credentials[found_cred]
        else:
            raise NotFound("Credentials not found")

        actor_obj.alt_ids.remove("UNAME:" + credentials_name)

        # Lower level RR call to avoid credentials clearing
        self.rr.update(actor_obj)

    def find_actor_identity_by_username(self, username=''):
        if not username:
            raise BadRequest("Invalid username")
        res_ids, _ = self.rr.find_resources_ext(alt_id_ns="UNAME", alt_id=username, id_only=True)
        if not res_ids:
            raise NotFound("No actor found with username")
        return res_ids[0]

    def is_user_existing(self, username=''):
        if not username:
            raise BadRequest("Invalid username")
        res_ids, _ = self.rr.find_resources_ext(alt_id_ns="UNAME", alt_id=username, id_only=True)
        return bool(res_ids)

    def set_actor_credentials(self, actor_id='', username='', password=''):
        if not username:
            raise BadRequest("Invalid username")
        IdentityUtils.check_password_policy(password)
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)
        cred_obj = None
        for cred in actor_obj.credentials:
            if cred.username == username:
                cred_obj = cred
                break
        if not cred_obj:
            cred_obj = Credentials()
            cred_obj.username = username
            actor_obj.credentials.append(cred_obj)
            actor_obj.alt_ids.append("UNAME:" + username)

        self._generate_password_hash(cred_obj, password)

        # Lower level RR call to avoid credentials clearing
        self.rr.update(actor_obj)

    def set_user_password(self, username='', password=''):
        if not username:
            raise BadRequest("Invalid username")
        IdentityUtils.check_password_policy(password)
        actor_id = self.find_actor_identity_by_username(username)
        actor_obj = self.read_actor_identity(actor_id)

        cred_obj = None
        for cred in actor_obj.credentials:
            if cred.username == username:
                cred_obj = cred
                break

        self._generate_password_hash(cred_obj, password)

        # Lower level RR call to avoid credentials clearing
        self.rr.update(actor_obj)

    def _generate_password_hash(self, cred_obj, password):
        if not cred_obj or cred_obj.type_ != OT.Credentials:
            raise BadRequest("Invalid cred_obj")
        cred_obj.identity_provider = "SciON"
        cred_obj.authentication_service = "SciON IdM"
        cred_obj.password_salt = bcrypt.gensalt()
        cred_obj.password_hash = bcrypt.hashpw(password, cred_obj.password_salt)

    def check_actor_credentials(self, username='', password=''):
        if not username:
            raise BadRequest("Invalid argument username")
        if not password:
            raise BadRequest("Invalid argument password")

        actor_id = self.find_actor_identity_by_username(username)
        actor_obj = self.read_actor_identity(actor_id)
        try:
            if actor_obj.auth_status != AuthStatusEnum.ENABLED:
                raise NotFound("Actor not enabled")

            cred_obj = None
            for cred in actor_obj.credentials:
                if cred.username == username:
                    cred_obj = cred
                    break

            if bcrypt.hashpw(password, cred_obj.password_salt) != cred_obj.password_hash:
                # Failed login
                if password:    # Only record fail if password is non-empty and wrong
                    actor_obj.auth_fail_count += 1
                    actor_obj.auth_ts_last_fail = get_ion_ts()
                    max_fail_cnt = IdentityUtils.get_auth_fail_lock_count()
                    if actor_obj.auth_fail_count > max_fail_cnt:
                        actor_obj.auth_status = AuthStatusEnum.LOCKED

                raise NotFound("Invalid password")

            # Success
            actor_obj.auth_count += 1
            actor_obj.auth_fail_count = 0
            actor_obj.auth_ts_last = get_ion_ts()

            return actor_obj._id

        finally:
            # Lower level RR call to avoid credentials clearing
            self.rr.update(actor_obj)

            self._publish_auth_event(actor_obj, username)

    def _publish_auth_event(self, actor_obj, username):
        if not self.event_pub:
            return

        event_data = dict()
        event_data['origin'] = actor_obj._id
        event_data['origin_type'] = RT.ActorIdentity
        event_data['sub_type'] = "SUCCESS" if actor_obj.auth_fail_count == 0 else "FAIL"
        event_data['username'] = username
        event_data['auth_count'] = actor_obj.auth_count
        event_data['auth_status'] = actor_obj.auth_status

        self.event_pub.publish_event(event_type='AuthenticationEvent', **event_data)

    def set_actor_auth_status(self, actor_id='', status=None):
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)
        if not status:
            raise BadRequest("Invalid argument status")

        prev_status = actor_obj.auth_status
        actor_obj.auth_status = status

        if status == AuthStatusEnum.ENABLED:
            actor_obj.auth_fail_count = 0

        # Lower level RR call to avoid credentials clearing
        self.rr.update(actor_obj)

        return prev_status

    def request_password_reset(self, username=''):
        actor_id = self.find_actor_identity_by_username(username)
        actor = self.rr.read(actor_id)

        validity = CFG.get_safe(CFG_PREFIX + '.password_token_validity', 60*60)
        actor.passwd_reset_token = self._create_token(actor_id=actor_id, validity=validity,
                                                      token_type=TokenTypeEnum.ACTOR_RESET_PASSWD)
        self.rr.update(actor)

        return actor.passwd_reset_token.token_string

    def reset_password(self, username='', token_string='', new_password=''):
        actor_id = self.find_actor_identity_by_username(username)
        actor = self.rr.read(actor_id)
        if not actor.passwd_reset_token or actor.passwd_reset_token.status != 'OPEN':
            raise Unauthorized("Token status invalid")
        cur_time = get_ion_ts_millis()
        if cur_time >= int(actor.passwd_reset_token.expires):
            raise Unauthorized("Token expired")
        if actor.passwd_reset_token.token_string != token_string:
            raise Unauthorized("Password reset token_string does not match")

        # Update password
        self.set_user_password(username, new_password)

        # Invalidate token after success
        actor = self.rr.read(actor_id)   # Read again, resource was updated in between
        actor.passwd_reset_token = None
        self.rr.update(actor)


    # -------------------------------------------------------------------------
    # Identity details (user profile) handling

    def define_identity_details(self, actor_id='', identity_details=None):
        actor_obj = self._validate_resource_id("actor_id", actor_id, RT.ActorIdentity)
        if not identity_details:
            raise BadRequest("Invalid argument identity_details")
        if actor_obj.details:
            if actor_obj.details.type_ != identity_details.type_:
                raise BadRequest("Type for identity_details does not match")
        actor_obj.details = identity_details

        self.update_actor_identity(actor_obj)

    def read_identity_details(self, actor_id=''):
        actor_obj = self.read_actor_identity(actor_id)

        return actor_obj.details


    # -------------------------------------------------------------------------
    # Manage tokens - authentication and others
    # TODO: Make more compliant with OAuth2, use HMAC, JWT etc

    def _create_token(self, actor_id='', start_time='', validity=0,
                     token_type=TokenTypeEnum.ACTOR_AUTH):
        if not actor_id:
            raise BadRequest("Must provide argument: actor_id")
        actor_obj = self.rr.read(actor_id)
        if actor_obj.type_ != RT.ActorIdentity:
            raise BadRequest("Illegal type for argument actor_id")
        if type(validity) not in (int, long):
            raise BadRequest("Illegal type for argument validity")
        if validity <= 0 or validity > MAX_TOKEN_VALIDITY:
            raise BadRequest("Illegal value for argument validity")
        cur_time = get_ion_ts_millis()
        if not start_time:
            start_time = cur_time
        start_time = int(start_time)
        if start_time > cur_time:
            raise BadRequest("Illegal value for start_time: Future values not allowed")
        if (start_time + 1000*validity) < cur_time:
            raise BadRequest("Illegal value for start_time: Already expired")
        expires = str(start_time + 1000*validity)

        token = self._generate_auth_token(actor_id, expires=expires, token_type=token_type)
        token_id = "token_%s" % token.token_string

        self.container.object_store.create(token, token_id)

        return token

    def _generate_auth_token(self, actor_id=None, expires="",
                             token_type=TokenTypeEnum.ACTOR_AUTH):
        token_string = uuid4().hex
        token = SecurityToken(token_type=token_type, token_string=token_string,
                              actor_id=actor_id, expires=expires, status="OPEN")
        return token

    def create_authentication_token(self, actor_id='', start_time='', validity=0):
        """Create an authentication token for provided actor id with a given start time and validity.
        start_time defaults to current time if empty and uses a system timestamp.
        validity is in seconds and must be set.
        """
        return self._create_token(actor_id, start_time, validity).token_string

    def read_authentication_token(self, token_string=''):
        """Returns the token object for given actor authentication token string.
        """
        token_id = "token_%s" % token_string
        token = self.container.object_store.read(token_id)
        if not isinstance(token, SecurityToken):
            raise Inconsistent("Token illegal type")
        return token

    def update_authentication_token(self, token=None):
        """Updates the given token.
        """
        if not isinstance(token, SecurityToken):
            raise BadRequest("Illegal argument type: token")
        if token.token_type != TokenTypeEnum.ACTOR_AUTH:
            raise BadRequest("Argument token: Illegal type")
        cur_time = get_ion_ts_millis()
        token_exp = int(token.expires)
        if token_exp > cur_time + 1000*MAX_TOKEN_VALIDITY:
            raise BadRequest("Argument token: Maximum expiry extended")

        self.container.object_store.update(token)

    def invalidate_authentication_token(self, token_string=''):
        """Invalidates an authentication token, but leaves it in place for auditing purposes.
        """
        token_id = "token_%s" % token_string
        token = self.container.object_store.read(token_id)
        if not isinstance(token, SecurityToken):
            raise Inconsistent("Token illegal type")
        if token.token_type != TokenTypeEnum.ACTOR_AUTH:
            raise BadRequest("Illegal token type")
        token.status = "INVALID"
        self.container.object_store.update(token)
        log.info("Invalidated security auth token: %s", token.token_string)

    def check_authentication_token(self, token_string=''):
        """Checks given token and returns a dict with actor id if valid.
        """
        token_id = "token_%s" % token_string
        token = self.container.object_store.read(token_id)
        if not isinstance(token, SecurityToken):
            raise Inconsistent("Token illegal type")
        if token.token_type != TokenTypeEnum.ACTOR_AUTH:
            raise BadRequest("Illegal token type")
        if token.token_string != token_string:
            raise Inconsistent("Found token's token_string does not match")
        cur_time = get_ion_ts_millis()
        if token.status != "OPEN":
            raise Unauthorized("Token status invalid")
        if cur_time >= int(token.expires):
            raise Unauthorized("Token expired")

        token_info = dict(actor_id=token.actor_id,
                    expiry=token.expires,
                    token=token,
                    token_id=token_id)

        log.info("Authentication token %s resolved to actor %s, expiry %s", token_string, token.actor_id, token.expires)

        return token_info

    def _get_actor_authentication_tokens(self, actor_id):
        actor_tokens = []
        raise NotImplementedError("TODO")