Beispiel #1
0
    def test_create_counter_proposal(self):

        with self.assertRaises(BadRequest) as cm:
            consumer_accept_sap = Negotiation.create_counter_proposal(proposal_status=ProposalStatusEnum.INITIAL)
        self.assertIn('The negotiation parameter must be a valid Negotiation object',cm.exception.message)

        negotiation_handler = Negotiation(self)

        sap = IonObject(OT.EnrollmentProposal,consumer=self.actor_identity._id, provider=self.org._id )

        negotiation = Mock()
        negotiation._id = '456'
        negotiation.type_ = RT.Negotiation
        negotiation.proposals = [sap]

        self.mock_read.return_value = negotiation
        self.mock_create.return_value = ['456', 2]

        neg_id = negotiation_handler.create_negotiation(sap)

        sap.negotiation_id = neg_id

        consumer_accept_sap = Negotiation.create_counter_proposal(negotiation, proposal_status=ProposalStatusEnum.COUNTER,
                originator=ProposalOriginatorEnum.PROVIDER)

        self.assertEqual(consumer_accept_sap.negotiation_id, negotiation._id)
        self.assertEqual(len(negotiation.proposals),1)
        self.assertEqual(consumer_accept_sap.sequence_num, len(negotiation.proposals))
        self.assertEqual(consumer_accept_sap.proposal_status, ProposalStatusEnum.COUNTER)
        self.assertEqual(consumer_accept_sap.originator, ProposalOriginatorEnum.PROVIDER)
Beispiel #2
0
    def test_create_negotiation_fail_precondition(self):

        self.preconditions.check_method1.return_value = False
        self.accept_actions.accept_method.return_value = None

        negotiation_rules = {
            OT.EnrollmentProposal: {
                'pre_conditions': ['preconditions.check_method1(sap.consumer)', 'not preconditions.check_method2(sap.provider,sap.consumer)'],
                'accept_action': 'accept_actions.accept_method(sap.provider,sap.consumer)'
            }}
        negotiation_handler = Negotiation(self, negotiation_rules)


        sap = IonObject(OT.EnrollmentProposal,consumer=self.actor_identity._id, provider=self.org._id )

        negotiation = Mock()
        negotiation._id = '456'
        negotiation.type_ = RT.Negotiation
        negotiation.proposals = []

        self.mock_read.return_value = negotiation
        self.mock_create.return_value = ['456', 2]

        with self.assertRaises(BadRequest) as cm:
            neg_id = negotiation_handler.create_negotiation(sap)
        self.assertIn('A precondition for this request has not been satisfied: preconditions.check_method1(sap.consumer)',cm.exception.message)

        self.assertEqual(len(negotiation.proposals),0)

        self.assertEqual(self.preconditions.check_method1.called,True)
        self.assertEqual(self.preconditions.check_method2.called,False)
        self.assertEqual(self.accept_actions.accept_method.called,False)
Beispiel #3
0
    def test_create_negotiation(self):

        self.preconditions.check_method1.return_value = True
        self.preconditions.check_method2.return_value = False
        self.accept_actions.accept_method.return_value = None

        negotiation_rules = {
            OT.EnrollmentProposal: {
                'pre_conditions': ['preconditions.check_method1(sap.consumer)', 'not preconditions.check_method2(sap.provider,sap.consumer)'],
                'accept_action': 'accept_actions.accept_method(sap.provider,sap.consumer)'
            }}

        negotiation_handler = Negotiation(self, negotiation_rules)

        with self.assertRaises(BadRequest) as cm:
            negotiation_handler.create_negotiation()
        self.assertIn('The sap parameter must be a valid Service Agreement Proposal object',cm.exception.message)

        sap = IonObject(OT.EnrollmentProposal,consumer=self.actor_identity._id, provider=self.org._id )
        sap.sequence_num = 1  #Force an error
        with self.assertRaises(Inconsistent) as cm:
            negotiation_handler.create_negotiation(sap)
        self.assertIn('The specified Service Agreement Proposal has inconsistent status fields',cm.exception.message)

        sap = IonObject(OT.EnrollmentProposal,consumer=self.actor_identity._id, provider=self.org._id )
        sap.proposal_status = ProposalStatusEnum.COUNTER  #Force an error
        with self.assertRaises(Inconsistent) as cm:
            negotiation_handler.create_negotiation(sap)
        self.assertIn('The specified Service Agreement Proposal has inconsistent status fields',cm.exception.message)

        sap = IonObject(OT.EnrollmentProposal,consumer=self.actor_identity._id, provider=self.org._id )
        sap.negotiation_id = 'efefeff'  #Force an error
        with self.assertRaises(Inconsistent) as cm:
            negotiation_handler.create_negotiation(sap)
        self.assertIn('The specified Service Agreement Proposal cannot have a negotiation_id for an initial proposal',cm.exception.message)

        sap = IonObject(OT.EnrollmentProposal,consumer=self.actor_identity._id, provider=self.org._id )

        negotiation = Mock()
        negotiation._id = '456'
        negotiation.type_ = RT.Negotiation
        negotiation.proposals = []

        self.mock_read.return_value = negotiation
        self.mock_create.return_value = ['456', 2]

        neg_id = negotiation_handler.create_negotiation(sap)

        self.assertEqual(neg_id, negotiation._id)
        self.assertEqual(len(negotiation.proposals),0)

        self.assertEqual(self.preconditions.check_method1.called,True)
        self.assertEqual(self.preconditions.check_method2.called,True)
        self.assertEqual(self.accept_actions.accept_method.called,False)
Beispiel #4
0
    def test_read_negotiation(self):

        negotiation_handler = Negotiation(self)

        with self.assertRaises(BadRequest) as cm:
            negotiation_handler.read_negotiation()
        self.assertIn(
            'The sap parameter must be a valid Service Agreement Proposal object',
            cm.exception.message)

        sap = IonObject(OT.EnrollmentProposal,
                        consumer=self.actor_identity._id,
                        provider=self.org._id)

        with self.assertRaises(BadRequest) as cm:
            negotiation_handler.read_negotiation(sap)
        self.assertIn(
            'The Service Agreement Proposal object (sap) is missing a negotiation_id value',
            cm.exception.message)

        negotiation = Mock()
        negotiation._id = '456'

        sap.negotiation_id = negotiation._id

        self.mock_read.return_value = negotiation

        neg_obj = negotiation_handler.read_negotiation(sap)

        self.assertEqual(neg_obj, negotiation)
    def test_get_extended_user_identity(self):

        actor_identity_obj = IonObject("ActorIdentity", {"name": self.subject})
        actor_id = self.identity_management_service.create_actor_identity(actor_identity_obj)

        user_credentials_obj = IonObject("UserCredentials", {"name": self.subject})
        self.identity_management_service.register_user_credentials(actor_id, user_credentials_obj)

        user_info_obj = IonObject("UserInfo", {"name": "Foo"})
        user_info_id = self.identity_management_service.create_user_info(actor_id, user_info_obj)

        ion_org = self.org_client.find_org()

        #Build the Service Agreement Proposal to to request a role but never close it
        sap = IonObject(OT.RequestRoleProposal,consumer=actor_id, provider=ion_org._id, role_name=ORG_MANAGER_ROLE )
        sap_response = self.org_client.negotiate(sap)

        #Just grant the role anyway
        #self.org_client.grant_role(ion_org._id, actor_id, ORG_MANAGER_ROLE)

        with self.assertRaises(NotFound):
            self.identity_management_service.get_user_info_extension('That rug really tied the room together.')
        with self.assertRaises(BadRequest):
            self.identity_management_service.get_user_info_extension()

        #Check the user without the negotiation role request
        extended_user = self.identity_management_service.get_user_info_extension(user_info_id, org_id=ion_org._id)
        self.assertEqual(user_info_obj.type_,extended_user.resource.type_)
        self.assertEqual(len(extended_user.roles),1)
        self.assertEqual(len(extended_user.open_requests),1)
        self.assertEqual(extended_user.open_requests[0].org_id, ion_org._id)
        self.assertEqual(extended_user.open_requests[0].user_id, user_info_id)
        self.assertEqual(extended_user.open_requests[0].request_type, OT.RequestRoleProposal)
        self.assertEqual(len(extended_user.closed_requests),0)
        self.assertEqual(extended_user.open_requests[0]._id, extended_user.open_requests[0].negotiation_id)

        neg = self.resource_registry.read(object_id=extended_user.open_requests[0].negotiation_id)
        sap_response = Negotiation.create_counter_proposal(neg, ProposalStatusEnum.ACCEPTED, ProposalOriginatorEnum.PROVIDER)
        sap_response2 = self.org_client.negotiate(sap_response)

        #Now check the user after the negotiation has been accepted and the role granted
        extended_user = self.identity_management_service.get_user_info_extension(user_info_id, org_id=ion_org._id)
        self.assertEqual(user_info_obj.type_,extended_user.resource.type_)
        self.assertEqual(len(extended_user.roles),2)
        self.assertEqual(len(extended_user.open_requests),0)
        self.assertEqual(len(extended_user.closed_requests),1)
        self.assertEqual(extended_user.closed_requests[0].org_id, ion_org._id)
        self.assertEqual(extended_user.closed_requests[0].user_id, user_info_id)
        self.assertEqual(extended_user.closed_requests[0].request_type, OT.RequestRoleProposal)

        self.identity_management_service.delete_user_info(user_info_id)

        self.org_client.revoke_role(org_id=ion_org._id, actor_id=actor_id, role_name=ORG_MANAGER_ROLE)

        self.identity_management_service.unregister_user_credentials(actor_id, self.subject)

        self.identity_management_service.delete_actor_identity(actor_id)
    def test_get_extended_user_identity(self):

        actor_identity_obj = IonObject("ActorIdentity", {"name": self.subject})
        actor_id = self.identity_management_service.create_actor_identity(actor_identity_obj)

        user_credentials_obj = IonObject("UserCredentials", {"name": self.subject})
        self.identity_management_service.register_user_credentials(actor_id, user_credentials_obj)

        user_info_obj = IonObject("UserInfo", {"name": "Foo"})
        user_info_id = self.identity_management_service.create_user_info(actor_id, user_info_obj)

        ion_org = self.org_client.find_org()

        #Build the Service Agreement Proposal to to request a role but never close it
        sap = IonObject(OT.RequestRoleProposal,consumer=actor_id, provider=ion_org._id, role_name=ORG_MANAGER_ROLE )
        sap_response = self.org_client.negotiate(sap)

        #Just grant the role anyway
        #self.org_client.grant_role(ion_org._id, actor_id, ORG_MANAGER_ROLE)

        with self.assertRaises(NotFound):
            self.identity_management_service.get_user_info_extension('That rug really tied the room together.')
        with self.assertRaises(BadRequest):
            self.identity_management_service.get_user_info_extension()

        #Check the user without the negotiation role request
        extended_user = self.identity_management_service.get_user_info_extension(user_info_id, org_id=ion_org._id)
        self.assertEqual(user_info_obj.type_,extended_user.resource.type_)
        self.assertEqual(len(extended_user.roles),1)
        self.assertEqual(len(extended_user.open_requests),1)
        self.assertEqual(extended_user.open_requests[0].org_id, ion_org._id)
        self.assertEqual(extended_user.open_requests[0].user_id, user_info_id)
        self.assertEqual(extended_user.open_requests[0].request_type, OT.RequestRoleProposal)
        self.assertEqual(len(extended_user.closed_requests),0)
        self.assertEqual(extended_user.open_requests[0]._id, extended_user.open_requests[0].negotiation_id)

        neg = self.resource_registry.read(object_id=extended_user.open_requests[0].negotiation_id)
        sap_response = Negotiation.create_counter_proposal(neg, ProposalStatusEnum.ACCEPTED, ProposalOriginatorEnum.PROVIDER)
        sap_response2 = self.org_client.negotiate(sap_response)

        #Now check the user after the negotiation has been accepted and the role granted
        extended_user = self.identity_management_service.get_user_info_extension(user_info_id, org_id=ion_org._id)
        self.assertEqual(user_info_obj.type_,extended_user.resource.type_)
        self.assertEqual(len(extended_user.roles),2)
        self.assertEqual(len(extended_user.open_requests),0)
        self.assertEqual(len(extended_user.closed_requests),1)
        self.assertEqual(extended_user.closed_requests[0].org_id, ion_org._id)
        self.assertEqual(extended_user.closed_requests[0].user_id, user_info_id)
        self.assertEqual(extended_user.closed_requests[0].request_type, OT.RequestRoleProposal)

        self.identity_management_service.delete_user_info(user_info_id)

        self.org_client.revoke_role(org_id=ion_org._id, actor_id=actor_id, role_name=ORG_MANAGER_ROLE)

        self.identity_management_service.unregister_user_credentials(actor_id, self.subject)

        self.identity_management_service.delete_actor_identity(actor_id)
Beispiel #7
0
    def test_create_negotiation_fail_precondition(self):

        self.preconditions.check_method1.return_value = False
        self.accept_actions.accept_method.return_value = None

        negotiation_rules = {
            OT.EnrollmentProposal: {
                'pre_conditions': [
                    'preconditions.check_method1(sap.consumer)',
                    'not preconditions.check_method2(sap.provider,sap.consumer)'
                ],
                'accept_action':
                'accept_actions.accept_method(sap.provider,sap.consumer)'
            }
        }
        negotiation_handler = Negotiation(self, negotiation_rules)

        sap = IonObject(OT.EnrollmentProposal,
                        consumer=self.actor_identity._id,
                        provider=self.org._id)

        negotiation = Mock()
        negotiation._id = '456'
        negotiation.type_ = RT.Negotiation
        negotiation.proposals = []

        self.mock_read.return_value = negotiation
        self.mock_create.return_value = ['456', 2]

        with self.assertRaises(BadRequest) as cm:
            neg_id = negotiation_handler.create_negotiation(sap)
        self.assertIn(
            'A precondition for this request has not been satisfied: preconditions.check_method1(sap.consumer)',
            cm.exception.message)

        self.assertEqual(len(negotiation.proposals), 0)

        self.assertEqual(self.preconditions.check_method1.called, True)
        self.assertEqual(self.preconditions.check_method2.called, False)
        self.assertEqual(self.accept_actions.accept_method.called, False)
Beispiel #8
0
    def test_create_counter_proposal(self):

        with self.assertRaises(BadRequest) as cm:
            consumer_accept_sap = Negotiation.create_counter_proposal(
                proposal_status=ProposalStatusEnum.INITIAL)
        self.assertIn(
            'The negotiation parameter must be a valid Negotiation object',
            cm.exception.message)

        negotiation_handler = Negotiation(self)

        sap = IonObject(OT.EnrollmentProposal,
                        consumer=self.actor_identity._id,
                        provider=self.org._id)

        negotiation = Mock()
        negotiation._id = '456'
        negotiation.type_ = RT.Negotiation
        negotiation.proposals = [sap]

        self.mock_read.return_value = negotiation
        self.mock_create.return_value = ['456', 2]

        neg_id = negotiation_handler.create_negotiation(sap)

        sap.negotiation_id = neg_id

        consumer_accept_sap = Negotiation.create_counter_proposal(
            negotiation,
            proposal_status=ProposalStatusEnum.COUNTER,
            originator=ProposalOriginatorEnum.PROVIDER)

        self.assertEqual(consumer_accept_sap.negotiation_id, negotiation._id)
        self.assertEqual(len(negotiation.proposals), 1)
        self.assertEqual(consumer_accept_sap.sequence_num,
                         len(negotiation.proposals))
        self.assertEqual(consumer_accept_sap.proposal_status,
                         ProposalStatusEnum.COUNTER)
        self.assertEqual(consumer_accept_sap.originator,
                         ProposalOriginatorEnum.PROVIDER)
def resolve_org_negotiation():
    try:
        payload = request.form['payload']
        json_params = simplejson.loads(str(payload))

        ion_actor_id, expiry = get_governance_info_from_request(
            'serviceRequest', json_params)
        ion_actor_id, expiry = validate_request(ion_actor_id, expiry)
        headers = build_message_headers(ion_actor_id, expiry)

        # extract negotiation-specific data (convert from unicode just in case - these are machine generated and unicode specific
        # chars are unexpected)
        verb = str(json_params['verb'])
        originator = str(json_params['originator'])
        negotiation_id = str(json_params['negotiation_id'])
        reason = str(json_params.get('reason', ''))

        proposal_status = None
        if verb.lower() == "accept":
            proposal_status = ProposalStatusEnum.ACCEPTED
        elif verb.lower() == "reject":
            proposal_status = ProposalStatusEnum.REJECTED

        proposal_originator = None
        if originator.lower() == "consumer":
            proposal_originator = ProposalOriginatorEnum.CONSUMER
        elif originator.lower() == "provider":
            proposal_originator = ProposalOriginatorEnum.PROVIDER

        rr_client = ResourceRegistryServiceProcessClient(
            node=Container.instance.node, process=service_gateway_instance)
        negotiation = rr_client.read(negotiation_id, headers=headers)

        new_negotiation_sap = Negotiation.create_counter_proposal(
            negotiation, proposal_status, proposal_originator)

        org_client = OrgManagementServiceProcessClient(
            node=Container.instance.node, process=service_gateway_instance)
        resp = org_client.negotiate(new_negotiation_sap, headers=headers)

        # update reason if it exists
        if reason:
            # reload negotiation because it has changed
            negotiation = rr_client.read(negotiation_id, headers=headers)
            negotiation.reason = reason
            rr_client.update(negotiation)

        return gateway_json_response(resp)

    except Exception, e:
        return build_error_response(e)
def resolve_org_negotiation():
    try:
        payload              = request.form['payload']
        json_params          = json_loads(str(payload))

        ion_actor_id, expiry = get_governance_info_from_request('serviceRequest', json_params)
        ion_actor_id, expiry = validate_request(ion_actor_id, expiry)
        headers              = build_message_headers(ion_actor_id, expiry)

        # extract negotiation-specific data (convert from unicode just in case - these are machine generated and unicode specific
        # chars are unexpected)
        verb                 = str(json_params['verb'])
        originator           = str(json_params['originator'])
        negotiation_id       = str(json_params['negotiation_id'])
        reason               = str(json_params.get('reason', ''))

        proposal_status = None
        if verb.lower() == "accept":
            proposal_status = ProposalStatusEnum.ACCEPTED
        elif verb.lower() == "reject":
            proposal_status = ProposalStatusEnum.REJECTED

        proposal_originator = None
        if originator.lower() == "consumer":
            proposal_originator = ProposalOriginatorEnum.CONSUMER
        elif originator.lower() == "provider":
            proposal_originator = ProposalOriginatorEnum.PROVIDER

        rr_client = ResourceRegistryServiceProcessClient(process=service_gateway_instance)
        negotiation = rr_client.read(negotiation_id, headers=headers)

        new_negotiation_sap = Negotiation.create_counter_proposal(negotiation, proposal_status, proposal_originator)

        org_client = OrgManagementServiceProcessClient(process=service_gateway_instance)
        resp = org_client.negotiate(new_negotiation_sap, headers=headers)

        # update reason if it exists
        if reason:
            # reload negotiation because it has changed
            negotiation = rr_client.read(negotiation_id, headers=headers)
            negotiation.reason = reason
            rr_client.update(negotiation)

        return gateway_json_response(resp)

    except Exception as e:
        return build_error_response(e)
Beispiel #11
0
    def test_read_negotiation(self):

        negotiation_handler = Negotiation(self)

        with self.assertRaises(BadRequest) as cm:
            negotiation_handler.read_negotiation()
        self.assertIn('The sap parameter must be a valid Service Agreement Proposal object',cm.exception.message)

        sap = IonObject(OT.EnrollmentProposal,consumer=self.actor_identity._id, provider=self.org._id )

        with self.assertRaises(BadRequest) as cm:
            negotiation_handler.read_negotiation(sap)
        self.assertIn('The Service Agreement Proposal object (sap) is missing a negotiation_id value',cm.exception.message)

        negotiation = Mock()
        negotiation._id = '456'

        sap.negotiation_id = negotiation._id

        self.mock_read.return_value = negotiation

        neg_obj = negotiation_handler.read_negotiation(sap)

        self.assertEqual(neg_obj, negotiation)
Beispiel #12
0
    def test_create_negotiation(self):

        self.preconditions.check_method1.return_value = True
        self.preconditions.check_method2.return_value = False
        self.accept_actions.accept_method.return_value = None

        negotiation_rules = {
            OT.EnrollmentProposal: {
                'pre_conditions': [
                    'preconditions.check_method1(sap.consumer)',
                    'not preconditions.check_method2(sap.provider,sap.consumer)'
                ],
                'accept_action':
                'accept_actions.accept_method(sap.provider,sap.consumer)'
            }
        }

        negotiation_handler = Negotiation(self, negotiation_rules)

        with self.assertRaises(BadRequest) as cm:
            negotiation_handler.create_negotiation()
        self.assertIn(
            'The sap parameter must be a valid Service Agreement Proposal object',
            cm.exception.message)

        sap = IonObject(OT.EnrollmentProposal,
                        consumer=self.actor_identity._id,
                        provider=self.org._id)
        sap.sequence_num = 1  #Force an error
        with self.assertRaises(Inconsistent) as cm:
            negotiation_handler.create_negotiation(sap)
        self.assertIn(
            'The specified Service Agreement Proposal has inconsistent status fields',
            cm.exception.message)

        sap = IonObject(OT.EnrollmentProposal,
                        consumer=self.actor_identity._id,
                        provider=self.org._id)
        sap.proposal_status = ProposalStatusEnum.COUNTER  #Force an error
        with self.assertRaises(Inconsistent) as cm:
            negotiation_handler.create_negotiation(sap)
        self.assertIn(
            'The specified Service Agreement Proposal has inconsistent status fields',
            cm.exception.message)

        sap = IonObject(OT.EnrollmentProposal,
                        consumer=self.actor_identity._id,
                        provider=self.org._id)
        sap.negotiation_id = 'efefeff'  #Force an error
        with self.assertRaises(Inconsistent) as cm:
            negotiation_handler.create_negotiation(sap)
        self.assertIn(
            'The specified Service Agreement Proposal cannot have a negotiation_id for an initial proposal',
            cm.exception.message)

        sap = IonObject(OT.EnrollmentProposal,
                        consumer=self.actor_identity._id,
                        provider=self.org._id)

        negotiation = Mock()
        negotiation._id = '456'
        negotiation.type_ = RT.Negotiation
        negotiation.proposals = []

        self.mock_read.return_value = negotiation
        self.mock_create.return_value = ['456', 2]

        neg_id = negotiation_handler.create_negotiation(sap)

        self.assertEqual(neg_id, negotiation._id)
        self.assertEqual(len(negotiation.proposals), 0)

        self.assertEqual(self.preconditions.check_method1.called, True)
        self.assertEqual(self.preconditions.check_method2.called, True)
        self.assertEqual(self.accept_actions.accept_method.called, False)
Beispiel #13
0
    def test_update_negotiation(self):

        self.preconditions.check_method1.return_value = True
        self.preconditions.check_method2.return_value = False
        self.accept_actions.accept_method.return_value = None

        negotiation_rules = {
            OT.EnrollmentProposal: {
                'pre_conditions': [
                    'preconditions.check_method1(sap.consumer)',
                    'not preconditions.check_method2(sap.provider,sap.consumer)'
                ],
                'accept_action':
                'accept_actions.accept_method(sap.provider,sap.consumer)'
            }
        }

        negotiation_handler = Negotiation(self, negotiation_rules)

        with self.assertRaises(Inconsistent) as cm:
            negotiation_handler.update_negotiation()
        self.assertIn(
            'The Service Agreement Proposal must have a negotiation resource id associated with it',
            cm.exception.message)

        sap = IonObject(OT.EnrollmentProposal,
                        consumer=self.actor_identity._id,
                        provider=self.org._id)

        with self.assertRaises(Inconsistent) as cm:
            negotiation_handler.update_negotiation(sap)
        self.assertIn(
            'The Service Agreement Proposal must have a negotiation resource id associated with it',
            cm.exception.message)

        negotiation = Mock()
        negotiation._id = '456'
        negotiation.type_ = RT.Negotiation
        negotiation.proposals = []

        sap.negotiation_id = negotiation._id

        self.mock_read.return_value = negotiation
        self.mock_update.return_value = ['456', 2]

        neg_id = negotiation_handler.update_negotiation(sap)

        self.assertEqual(self.event_pub.publish_event.called, True)

        self.assertEqual(neg_id, negotiation._id)
        self.assertEqual(len(negotiation.proposals), 1)

        counter_sap = Negotiation.create_counter_proposal(
            negotiation, ProposalStatusEnum.REJECTED,
            ProposalOriginatorEnum.PROVIDER)

        neg_id = negotiation_handler.update_negotiation(
            counter_sap, 'Fake rejection reason')

        self.assertEqual(len(negotiation.proposals), 2)
        self.assertEqual(negotiation.negotiation_status,
                         NegotiationStatusEnum.REJECTED)
        self.assertEquals(negotiation.reason, 'Fake rejection reason')

        counter_sap = Negotiation.create_counter_proposal(
            negotiation, ProposalStatusEnum.ACCEPTED,
            ProposalOriginatorEnum.PROVIDER)

        neg_id = negotiation_handler.update_negotiation(counter_sap)
        self.assertEqual(len(negotiation.proposals), 3)
        self.assertEqual(negotiation.negotiation_status,
                         NegotiationStatusEnum.REJECTED)

        counter_sap = Negotiation.create_counter_proposal(
            negotiation, ProposalStatusEnum.ACCEPTED,
            ProposalOriginatorEnum.CONSUMER)

        neg_id = negotiation_handler.update_negotiation(counter_sap)
        self.assertEqual(len(negotiation.proposals), 4)
        self.assertEqual(negotiation.negotiation_status,
                         NegotiationStatusEnum.ACCEPTED)

        self.assertEqual(self.accept_actions.accept_method.called, True)
    def on_init(self):

        self.event_pub = EventPublisher()
        self.negotiation_handler = Negotiation(self, negotiation_rules, self.event_pub)
class OrgManagementService(BaseOrgManagementService):
    """
    Services to define and administer a facility (synonymous Org, community), 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.event_pub = EventPublisher()
        self.negotiation_handler = Negotiation(self, negotiation_rules, self.event_pub)

    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 _validate_parameters(self, **kwargs):

        parameter_objects = dict()

        org_id = None

        if kwargs.has_key('org_id'):
            org_id = kwargs['org_id']

            if not org_id:
                raise BadRequest("The org_id parameter is missing")

            org = self.clients.resource_registry.read(org_id)
            if not org:
                raise NotFound("Org %s does not exist" % org_id)

            parameter_objects['org'] = org

        if kwargs.has_key('actor_id'):
            actor_id = kwargs['actor_id']

            if not actor_id:
                raise BadRequest("The actor_id parameter is missing")

            actor = self.clients.resource_registry.read(actor_id)
            if not actor:
                raise NotFound("Actor %s does not exist" % actor)

            parameter_objects['actor'] = actor


        if kwargs.has_key('role_name'):
            role_name = kwargs['role_name']

            if not role_name:
                raise BadRequest("The role_name parameter is missing")

            if org_id is None:
                raise BadRequest("The org_id parameter is missing")

            user_role = self._find_role(org_id, role_name)
            if user_role is None:
                raise BadRequest("The User Role '%s' does not exist for this Org" % role_name)

            parameter_objects['user_role'] = user_role


        if kwargs.has_key('resource_id'):
            resource_id = kwargs['resource_id']

            if not resource_id:
                raise BadRequest("The resource_id parameter is missing")

            resource = self.clients.resource_registry.read(resource_id)
            if not resource:
                raise NotFound("Resource %s does not exist" % resource_id)

            parameter_objects['resource'] = resource


        if kwargs.has_key('negotiation_id'):
            negotiation_id = kwargs['negotiation_id']

            if not negotiation_id:
                raise BadRequest("The negotiation_id parameter is missing")

            negotiation = self.clients.resource_registry.read(negotiation_id)
            if not negotiation:
                raise NotFound("Negotiation %s does not exist" % negotiation_id)

            parameter_objects['negotiation'] = negotiation

        if kwargs.has_key('affiliate_org_id'):

            affiliate_org_id = kwargs['affiliate_org_id']

            if not affiliate_org_id:
                raise BadRequest("The affiliate_org_id parameter is missing")

            affiliate_org = self.clients.resource_registry.read(affiliate_org_id)
            if not affiliate_org:
                raise NotFound("Org %s does not exist" % affiliate_org_id)

            parameter_objects['affiliate_org'] = affiliate_org

        return parameter_objects

    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.

        @param org    Org
        @retval org_id    str
        @throws BadRequest    if object passed has _id or _rev attribute
        """

        if not org:
            raise BadRequest("The org parameter is missing")

        #Only allow one root ION Org in the system
        if org.name == self._get_root_org_name():
            res_list,_  = self.clients.resource_registry.find_resources(restype=RT.Org, name=self._get_root_org_name())
            if len(res_list) > 0:
                raise BadRequest('There can only be one Org named %s' % self._get_root_org_name())

        #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' can only contain alphanumeric and underscore characters" % org.org_governance_name)


        org_id, org_rev = self.clients.resource_registry.create(org)
        org._id = org_id
        org._rev = org_rev

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

        #Instantiate initial set of User Roles for this Org
        manager_role = IonObject(RT.UserRole, name='Facility Administrator', governance_name=ORG_MANAGER_ROLE, description='Change Facility Information, assign Roles, post Facility events')
        self.add_user_role(org_id, manager_role)

        member_role = IonObject(RT.UserRole, name='Facility Member', governance_name=ORG_MEMBER_ROLE, description='Subscribe to events, set personal preferences')
        self.add_user_role(org_id, member_role)

        return org_id

    def update_org(self, org=None):
        """Updates the Org based on provided object.

        @param org    Org
        @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
        """
        if not org:
            raise BadRequest("The org parameter is missing")

        #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' can only contain alphanumeric and underscore characters" % org.org_governance_name)

        self.clients.resource_registry.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.

        @param org_id    str
        @retval org    Org
        @throws NotFound    object with specified id does not exist
        """
        param_objects = self._validate_parameters(org_id=org_id)

        return param_objects['org']

    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.

        @param org_id    str
        @throws NotFound    object with specified id does not exist
        """
        if not org_id:
            raise BadRequest("The org_id parameter is missing")

        self.clients.resource_registry.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.

        @param name    str
        @retval org    Org
        @throws NotFound    Org with name does not exist
        """

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

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


    def add_user_role(self, org_id='', user_role=None):
        """Adds a UserRole to an Org. Will call Policy Management Service to actually
        create the role object that is passed in, if the role by the specified
        name does not exist. Throws exception if either id does not exist.

        @param org_id    str
        @param user_role    UserRole
        @retval user_role_id    str
        @throws NotFound    object with specified name does not exist
        """

        param_objects = self._validate_parameters(org_id=org_id)
        org = param_objects['org']

        if not user_role:
            raise BadRequest("The user_role parameter is missing")

        if self._find_role(org_id, user_role.governance_name) is not None:
            raise BadRequest("The user role '%s' is already associated with this Org" % user_role.governance_name)

        user_role.org_governance_name = org.org_governance_name
        user_role_id = self.clients.policy_management.create_role(user_role)

        aid = self.clients.resource_registry.create_association(org, PRED.hasRole, user_role_id)

        return user_role_id

    def remove_user_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
        Throws exception if either id does not exist.

        @param org_id    str
        @param name    str
        @param force_removal    bool
        @retval success    bool
        @throws NotFound    object with specified name does not exist
        """
        param_objects = self._validate_parameters(org_id=org_id, role_name=role_name)
        org = param_objects['org']
        user_role = param_objects['user_role']

        if not force_removal:
            alist,_ = self.clients.resource_registry.find_subjects(RT.ActorIdentity, PRED.hasRole, user_role)
            if len(alist) > 0:
                raise BadRequest('The User Role %s cannot be removed as there are %s users associated to it' %
                                 (user_role.name, str(len(alist))))


        #Finally remove the association to the Org
        aid = self.clients.resource_registry.get_association(org, PRED.hasRole, user_role)
        if not aid:
            raise NotFound("The role association between the specified Org (%s) and UserRole (%s) is not found" %
                           (org_id, user_role.name))

        self.clients.resource_registry.delete_association(aid)

        return True

    def find_org_role_by_name(self, org_id='', role_name=''):
        """Returns the User Role object for the specified name in the Org.
        Throws exception if name does not match any persisted User Role or the Org does not exist.
        objects.

        @param org_id    str
        @param name    str
        @retval user_role    UserRole
        @throws NotFound    object with specified name or if does not exist
        """
        param_objects = self._validate_parameters(org_id=org_id, role_name=role_name)
        user_role = param_objects['user_role']

        return user_role


    def _find_role(self, org_id='', role_name=''):

        if not org_id:
            raise BadRequest("The org_id parameter is missing")

        if not role_name:
            raise BadRequest("The governance_name parameter is missing")

        org_roles = self.find_org_roles(org_id)
        for role in org_roles:
            if role.governance_name == role_name:
                return role

        return None


    def find_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.

        @param org_id    str
        @retval user_role_list    list
        @throws NotFound    object with specified id does not exist
        """
        param_objects = self._validate_parameters(org_id=org_id)
        org = param_objects['org']

        role_list,_ = self.clients.resource_registry.find_objects(org, PRED.hasRole, RT.UserRole)

        return role_list

    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 Service Agreement Proposal is used to specify conditions of the proposal as well
        as counter proposals and the Org will create Negotiation Resource to track the history and status of the negotiation.

        @param sap    ServiceAgreementProposal
        @retval sap    ServiceAgreementProposal
        @throws BadRequest    If an SAP is not provided or incomplete
        @throws Inconsistent    If an SAP has inconsistent information
        @throws NotFound    If any of the ids in the SAP do not exist
        """

        if sap is None or ( sap.type_ != OT.ServiceAgreementProposal and not issubtype(sap.type_, OT.ServiceAgreementProposal)):
            raise BadRequest('The sap parameter must be a valid Service Agreement Proposal 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.clients.resource_registry.read(neg_id)

        #hardcodng 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.clients.resource_registry.read(neg_id)

            else:

                #Automatically reject the proposal if the exipration 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.clients.resource_registry.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.clients.resource_registry.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.clients.resource_registry.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.clients.resource_registry.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. Will throw a not NotFound exception
        if any of the specified ids do not exist.

        @param org_id    str
        @param proposal_type    str
        @param negotiation_status    str
        @retval negotiation    list
        @throws NotFound    object with specified id does not exist
        """
        param_objects = self._validate_parameters(org_id=org_id)

        neg_list,_ = self.clients.resource_registry.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.
        Will throw a not NotFound exception if any of the specified ids do not exist.

        @param org_id    str
        @param proposal_type    str
        @retval negotiation    list
        @throws NotFound    object with specified id does not exist
        """
        param_objects = self._validate_parameters(org_id=org_id)

        neg_list,_ = self.clients.resource_registry.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. Will throw a not NotFound exception
        if any of the specified ids do not exist.

        @param actor_id    str
        @param org_id    str
        @param proposal_type    str
        @param negotiation_status    str
        @retval negotiation    list
        @throws NotFound    object with specified id does not exist
        """
        param_objects = self._validate_parameters(actor_id=actor_id)
        actor = param_objects['actor']


        neg_list,_ = self.clients.resource_registry.find_objects(actor, PRED.hasNegotiation)

        if org_id:
            param_objects = self._validate_parameters(org_id=org_id)
            org = param_objects['org']

            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


    def enroll_member(self, org_id='', actor_id=''):
        """Enrolls a specified actor into the specified 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. Throws a NotFound exception if neither id is found.

        @param org_id    str
        @param actor_id    str
        @retval success    bool
        @throws NotFound    object with specified id does not exist
        """
        param_objects = self._validate_parameters(org_id=org_id, actor_id=actor_id)
        org = param_objects['org']
        actor = param_objects['actor']

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

        aid = self.clients.resource_registry.create_association(org, PRED.hasMembership, actor)


        if not aid:
            return False

        member_role = self.find_org_role_by_name(org._id,ORG_MEMBER_ROLE )
        self._add_role_association(org, actor, 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.name )

        return True

    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. Throws a NotFound exception if neither id is found.

        @param org_id    str
        @param actor_id    str
        @retval success    bool
        @throws NotFound    object with specified id does not exist
        """
        param_objects = self._validate_parameters(org_id=org_id, actor_id=actor_id)
        org = param_objects['org']
        actor = param_objects['actor']

        if org.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.find_org_roles_by_user(org_id, actor_id)
        for user_role in role_list:
            self._delete_role_association(org, actor, user_role)

        #Finally remove the association to the Org
        aid = self.clients.resource_registry.get_association(org, PRED.hasMembership, actor)
        if not aid:
            raise NotFound("The membership association between the specified actor and Org is not found")

        self.clients.resource_registry.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.name )

        return True

    def is_registered(self, actor_id=''):
        """Returns True if the specified actor_id is registered with the ION system; otherwise False.

        @param actor_id    str
        @retval is_registered    bool
        @throws BadRequest    if the actor_id is not specified.
        """
        if not actor_id:
            raise BadRequest("The actor_id parameter is missing")

        try:
            user = self.clients.resource_registry.read(actor_id)
            return True
        except Exception, e:
            log.error('is_registered: %s for actor_id:%s' %  (e.message, actor_id))

        return False
    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 Service Agreement Proposal is used to specify conditions of the proposal as well
        as counter proposals and the Org will create Negotiation Resource to track the history and status of the negotiation.

        @param sap    ServiceAgreementProposal
        @retval sap    ServiceAgreementProposal
        @throws BadRequest    If an SAP is not provided or incomplete
        @throws Inconsistent    If an SAP has inconsistent information
        @throws NotFound    If any of the ids in the SAP do not exist
        """

        if sap is None or ( sap.type_ != OT.ServiceAgreementProposal and not issubtype(sap.type_, OT.ServiceAgreementProposal)):
            raise BadRequest('The sap parameter must be a valid Service Agreement Proposal 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.clients.resource_registry.read(neg_id)

        #hardcodng 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.clients.resource_registry.read(neg_id)

            else:

                #Automatically reject the proposal if the exipration 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.clients.resource_registry.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.clients.resource_registry.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.clients.resource_registry.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.clients.resource_registry.read(neg_id)


        #Return the latest proposal
        return negotiation.proposals[-1]
Beispiel #17
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
Beispiel #18
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
Beispiel #19
0
    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 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
Beispiel #21
0
    def test_update_negotiation(self):

        self.preconditions.check_method1.return_value = True
        self.preconditions.check_method2.return_value = False
        self.accept_actions.accept_method.return_value = None

        negotiation_rules = {
            OT.EnrollmentProposal: {
                'pre_conditions': ['preconditions.check_method1(sap.consumer)', 'not preconditions.check_method2(sap.provider,sap.consumer)'],
                'accept_action': 'accept_actions.accept_method(sap.provider,sap.consumer)'
            }}

        negotiation_handler = Negotiation(self, negotiation_rules, self.event_pub)

        with self.assertRaises(Inconsistent) as cm:
            negotiation_handler.update_negotiation()
        self.assertIn('The Service Agreement Proposal must have a negotiation resource id associated with it',cm.exception.message)

        sap = IonObject(OT.EnrollmentProposal,consumer=self.actor_identity._id, provider=self.org._id )

        with self.assertRaises(Inconsistent) as cm:
            negotiation_handler.update_negotiation(sap)
        self.assertIn('The Service Agreement Proposal must have a negotiation resource id associated with it',cm.exception.message)

        negotiation = Mock()
        negotiation._id = '456'
        negotiation.type_ = RT.Negotiation
        negotiation.proposals = []

        sap.negotiation_id = negotiation._id

        self.mock_read.return_value = negotiation
        self.mock_update.return_value = ['456', 2]


        neg_id = negotiation_handler.update_negotiation(sap)

        self.assertEqual(self.event_pub.publish_event.called,True)

        self.assertEqual(neg_id, negotiation._id)
        self.assertEqual(len(negotiation.proposals),1)

        counter_sap = Negotiation.create_counter_proposal(negotiation, ProposalStatusEnum.REJECTED, ProposalOriginatorEnum.PROVIDER)

        neg_id = negotiation_handler.update_negotiation(counter_sap, 'Fake rejection reason')

        self.assertEqual(len(negotiation.proposals),2)
        self.assertEqual(negotiation.negotiation_status, NegotiationStatusEnum.REJECTED)
        self.assertEquals(negotiation.reason, 'Fake rejection reason' )

        counter_sap = Negotiation.create_counter_proposal(negotiation, ProposalStatusEnum.ACCEPTED, ProposalOriginatorEnum.PROVIDER)

        neg_id = negotiation_handler.update_negotiation(counter_sap)
        self.assertEqual(len(negotiation.proposals),3)
        self.assertEqual(negotiation.negotiation_status, NegotiationStatusEnum.REJECTED)

        counter_sap = Negotiation.create_counter_proposal(negotiation, ProposalStatusEnum.ACCEPTED, ProposalOriginatorEnum.CONSUMER)

        neg_id = negotiation_handler.update_negotiation(counter_sap)
        self.assertEqual(len(negotiation.proposals),4)
        self.assertEqual(negotiation.negotiation_status, NegotiationStatusEnum.ACCEPTED)

        self.assertEqual(self.accept_actions.accept_method.called,True)
    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 Service Agreement Proposal is used to specify conditions of the proposal as well
        as counter proposals and the Org will create Negotiation Resource to track the history and status of the negotiation.

        @param sap    ServiceAgreementProposal
        @retval sap    ServiceAgreementProposal
        @throws BadRequest    If an SAP is not provided or incomplete
        @throws Inconsistent    If an SAP has inconsistent information
        @throws NotFound    If any of the ids in the SAP do not exist
        """

        if sap is None or ( sap.type_ != OT.ServiceAgreementProposal and not issubtype(sap.type_, OT.ServiceAgreementProposal)):
            raise BadRequest('The sap parameter must be a valid Service Agreement Proposal object')

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

            #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.clients.resource_registry.read(neg_id)

        #hardcodng some rules at the moment
        if sap.type_ == OT.EnrollmentProposal or sap.type_ == OT.RequestRoleProposal:
            #Automatically accept for the consumer if the Org Manager as provider accepts the proposal
            if sap.proposal_status == ProposalStatusEnum.ACCEPTED and 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.clients.resource_registry.read(neg_id)

            elif sap.proposal_status == ProposalStatusEnum.ACCEPTED and 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.clients.resource_registry.read(neg_id)

        elif sap.type_ == OT.AcquireResourceExclusiveProposal:
            if not self.is_resource_acquired_exclusively(None, sap.resource):

                #Automatically reject the proposal if the exipration request is greater than 12 hours from now or 0
                cur_time = int(get_ion_ts())
                expiration = cur_time +  ( 12 * 60 * 60 * 1000 ) # 12 hours from now
                if sap.expiration == 0 or 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 included 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.clients.resource_registry.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.clients.resource_registry.read(neg_id)

                    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.clients.resource_registry.read(neg_id)

        #Return the latest proposal
        return negotiation.proposals[-1]
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