def PostObject(self, provider, object_type, payload): """ Request to write a Social Object to a given provider. Requests are verified against the privacy policy, and outgoing objects are sanitised as necessary. :param provider: Provider name :type provider: str :param object_type: Type of object to write :type object_type: str :param payload: Object to post to provider :type payload: Social Object """ headers = SARHeaders("POST", provider, object_type, payload) if not self.privacy_policy: raise Exception("Provide a privacy policy before"\ " making requests.") provider_gateway = self.__getServiceGateway(provider) processor = PolicyProcessor(self.privacy_policy) request_valid = processor._validate_object_request( "POST", provider, object_type, payload) gateway_attr = getattr(provider_gateway, object_type) response = gateway_attr("POST", payload) response_obj = SocialActivityResponse(response, headers) sanitised_response = processor._sanitise_object_request(response_obj)
def PostObject(self, provider, object_type, payload): """ Request to write a Social Object to a given provider. Requests are verified against the privacy policy, and outgoing objects are sanitised as necessary. :param provider: Provider name :type provider: str :param object_type: Type of object to write :type object_type: str :param payload: Object to post to provider :type payload: Social Object """ headers = SARHeaders("POST", provider, object_type, payload) if not self.privacy_policy: raise Exception("Provide a privacy policy before"\ " making requests.") provider_gateway = self.__getServiceGateway(provider) processor = PolicyProcessor(self.privacy_policy) request_valid = processor._validate_object_request("POST", provider, object_type, payload) gateway_attr = getattr(provider_gateway, object_type) response = gateway_attr("POST",payload) response_obj = SocialActivityResponse(response, headers) sanitised_response = processor._sanitise_object_request(response_obj)
def provide_privacy_policy(self, privacy_policy): """ Provide the privacy policy for this experiment. Used to instantiate an instance of PolicyProcessor. This can only be done once for an instance of SocialObjectGateway. This must be called before attempting to read or write Social Objects. :param privacy_policy: path to a privacy policy XML file :type privacy_policy: str """ if self.privacy_policy: raise Exception("Privacy policy already defined. If \ you need to change it, start a new experiment.") self.privacy_policy = privacy_policy self.policy_processor = PolicyProcessor(self.privacy_policy, self)
class SocialObjectsGateway(object): """ This is a friendlier interface to PRISONER's internals, which participation clients should access. This coordinates access to other service gateways, and the management of experimental responses. A single instance of this object should be maintained throughout the lifecycle of an experimental application. """ def __init__(self, server_url=None): self.privacy_policy = None self.exp_design = None # dict keyed on provider names, with values access tokens. this # should be persisted so auth not needed on each # session for the same participant self.keychain = {} # dict keyed on provider names, instances of service gateways self.gateways = {} # maintains a PersistenceManager for DB interaction self.persistence = None self.policy_processor = None self.participant = None # stores instances of SocialObjects under a unique id so clients # can refer to previous instances. TODO: this needs a simple # layer of authentication. is the current user the owner of this # cached object? self.cached_objects = {} # internal cache used to store *unsanitised* # responses to avoid making duplicate network # requests. ONLY for internal use, NEVER directly # access the cache self.internal_cache = {} self.server_url = server_url def cache_object(self, object_to_cache): """ Generates a unique identifier for this object, caches it, and returns the identifier. :param object_to_cache: SocialObject to cache :type object_to_cache: SocialObject :returns: str -- object's identifier """ ident = str(uuid.uuid4()) while (ident in self.cached_objects): ident = str(uuid.uuid4()) object_to_cache.prisoner_id = ident self.cached_objects[ident] = object_to_cache return ident def request_authentication(self, provider, callback): """ Call this if it is necessary to perform authenticated API calls with a service gateway (usually required to write data as a person or to read sensitive data). Each service gateway has its own res mechanism. Calling this will return a token needed to proceed with authentication. Authentication is completed by presenting a relevant interface to users, then calling complete_authentication() with its token. :param provider: Name of provider to authenticate with. :type provider: str. :param callback: URL to let PRISONER authentication server know user has provided authentication :type callback: str. :returns: URL required to complete authentication""" if provider in self.keychain: return None # attempt to find this gateway gateway = self.__getServiceGateway(provider) authent_url = gateway.request_authentication(callback) return authent_url # what url do i need to authetnicate? # let the user consume the authent url and come back in their # own time def register_participant(self, schema, participant): participant = self.persistence.register_participant( schema, participant) self.participant = participant return participant def get_participant(self): return self.participant def complete_authentication(self, provider, request=None): """ Completes the second stage of authentication with a provider. :param provider: Name of provider to authenticate with. :type provider: str. :param request: The request received from the provider when it called the PRISONER callback. This should contain any parameters needed to complete authentication :type request: werkzeug Request """ gateway = self.__getServiceGateway(provider) ret_access_token = gateway.complete_authentication(request) self.persistence.register_participant_with_provider( self.participant[0], provider, ret_access_token) return None def restore_authentication(self, provider, access_token): """ Attempt to provide a service gateway with an existing access token (eg. stored in DB) to authenticate without going through clientside flow. Returns boolean value to indicate success. If False, a call should be made to requst_authentication() to begin clientside flow. :param provider: Name of provider to authenticate with :type provider: str :param access_token: Object used to authenticate with this provider :type access_token: object :returns boolean - was authentication attempt successful? """ gateway = self.__getServiceGateway(provider) auth_success = gateway.restore_authentication(access_token) return auth_success def provide_privacy_policy(self, privacy_policy): """ Provide the privacy policy for this experiment. Used to instantiate an instance of PolicyProcessor. This can only be done once for an instance of SocialObjectGateway. This must be called before attempting to read or write Social Objects. :param privacy_policy: path to a privacy policy XML file :type privacy_policy: str """ if self.privacy_policy: raise Exception("Privacy policy already defined. If \ you need to change it, start a new experiment.") self.privacy_policy = privacy_policy self.policy_processor = PolicyProcessor(self.privacy_policy, self) def provide_experimental_design(self, experimental_design, connection_string): """ Provide the experimental design for this experiment. Used to instantiate a PersistenceManager. Can only be done once per instance of SocialObjectGateway. This must be called before attemtping to persist any response data. :param experimental_design: path to an experimental design XML file :type experimental_design: str :param connection_string: database connection string for persisting data :type connection_string: str """ if self.persistence: raise Exception("Experimental design already defined."+\ "If you need to change it, start a new experiment") self.persistence = PersistenceManager.PersistenceManager( experimental_design, self.policy_processor, connection_string) self.props = self.persistence.props def post_response(self, schema, response): """ Passes the response to the PersistenceManager to write to the internal database. There must be an experimental design bound first. :param schema: Name of the response table to write to :type schema: str. :param response: The response dictionary to write to the specified schema :type response: dict """ if not self.persistence: raise Exception("No experimental design supplied") self.persistence.post_response(schema, response) def GetObjectJSON(self, provider, object_type, payload, criteria, extra_args=None): """ Interface for retrieving objects from a service gateway, for consumption by web services. This differs from GetObject in some fundamental ways. GetObject is more pythonic - you request objects by supplying relevant SocialObjects, and you get SocialObject instances in return. This method however, receives plain-text responses, and returns JSON objects. Whereas GetObject expects a semantically-appropriate SocialObject as the payload (eg. supply an instance of Person to receive objects of a given type owned by that Person), this method expects a payload expressed as a query string, using the namespaced syntax found in the privacy policy spec. For example, a payload of "session:Lastfm.id" will be evaluated as "get objects authored by the user ID in the Last.fm session. "literal:lukeweb", similarly, returns objects owned by that literal user. JSON objects are returned, with the same fields as the Pythonic counterparts. A key difference is that the returned object has an additional attribute injected - prisoner_id. This is a unique identifier for the returned object *that is valid for the duration of this session*. Rather than passing around full instances of objects, subsequent queries, or publication of experimental responses, need only refer to this ID to ensure PRISONER is able to relate your requests back to the full representation of the data. Note that subsequent attempts to retrieve the cached object are subject to the privacy policy sanitisation process of the *original* request. """ # evaluate payload eval_payload = self.policy_processor._infer_object(payload) eval_payload_obj = SocialObjects.SocialObject() eval_payload_obj.id = eval_payload # use this to passthrough original object, not wrapped # as a dumb SocialObject eval_payload_obj = eval_payload # call getobject with cleaned object ret_object = self.GetObject(provider, object_type, eval_payload_obj, True, criteria, extra_args) # cache the object under a unique id, JSONify, return if ret_object != None: ident = self.cache_object(ret_object) try: return jsonpickle.encode(self.cached_objects[ident]) except: return None else: return None def PostObjectJSON(self, provider, object_type, payload): """ Used by web services interface for pushing objects to a service gateway. Expects a payload as a JSON dictionary, where the keys are the appropriate fields of <object_type> This method converts the dictionary to a native object and pushes it through the PRISONER pipe for sanitisation and publication """ dumb_social = SocialObjects.SocialObject() payload = json.loads(payload) for key, value in payload.items(): setattr(dumb_social, key, value) self.PostObject(provider, object_type, dumb_social) def __add_to_cache(self, key, to_cache): """ Make a deep copy of the given object and write it to the internal cache. :param key: Key to cache object under :type key: str :param to_cache: Object to cache :type to_cache: object """ self.internal_cache[key] = copy.deepcopy(to_cache) def GetObject(self, provider, object_type, payload, allow_many=False, criteria=None, extra_args=None): """ Interface for retrieving an object from a service gateway. Requests are verified against the privacy policy, and returned objects are sanitised as appropriate. The payload and filter arguments are semantically distinct. See the documentation for each argument to understand how to use them. :param provider: name of provider to get object from :type provider: str :param object_type: name of object to get :type object_type: str :param payload: This must contain a SocialObject or dictionary of arguments necessary for the ServiceGateway to make a meaningful request. For example, it may be a user ID to retrieve their photos, however it should not contain criteria for filtering the objects returned (see criteria). The expected payload depends on the ServiceGateway and the objects you are requesting. See the documentation for each object exposed by the ServiceGateway to see the payload it requests. :type payload: object :param criteria: Optional criteria for filtering the objects returned by the ServiceGateway. This expression is run on all objects returned by gateway, and only where it evaluates as True is the object returned. Uses syntax similar to lambda expressions, without prefix. x is used to refer to each instance of an object. eg. '"party" in x.tags' :type filter: str :returns: SocialObject -- sanitised for consumption by participation client """ headers = SARHeaders("GET", provider, object_type, payload) if not self.privacy_policy: raise Exception("Provide a privacy policy before"\ " making requests.") provider_gateway = self.__getServiceGateway(provider) processor = self.policy_processor object_type = processor._validate_object_request( "GET", provider, object_type, payload) if "%s_%s" % (object_type, payload) not in self.internal_cache: gateway_attr = getattr(provider_gateway, object_type) request_handler = getattr(provider_gateway, "request_handler") response = request_handler(gateway_attr, "GET", payload, extra_args) self.__add_to_cache("%s_%s" % (object_type, payload), response) else: response = self.internal_cache["%s_%s" % (object_type, payload)] sanitised_set = [] if hasattr(response.social_object, "objects"): #is a Collection new_coll = response.social_object if criteria: lambda_func = eval("lambda x: %s" % criteria) response.social_object.objects = filter( lambda_func, response.social_object.objects) for resp in response.social_object.objects: resp.provider = provider response_obj = SocialActivityResponse(resp, headers) sanitised_response = processor._sanitise_object_request( response_obj) sanitised_response.prisoner_id = self.cache_object( sanitised_response) sanitised_set.append(sanitised_response) new_coll.objects = sanitised_set new_coll.prisoner_id = self.cache_object(new_coll) return new_coll else: if criteria: response_set = [response.social_object] response.social_object = filter( eval("lambda x: %s" % criteria), response.social_object.objects) response.provider = provider headers.wrapped_headers = response.headers response_obj = SocialActivityResponse(response.social_object, headers) sanitised_response = processor._sanitise_object_request( response_obj) sanitised_response.prisoner_id = self.cache_object( sanitised_response) sanitised_response.headers = headers return sanitised_response def get_service_gateway(self, provider): """ External wrapper to internal function """ return self.__getServiceGateway(provider) def __getServiceGateway(self, provider): """ Given the name of a provider, return an instance of the appropriate service gateway :param provider: Provider name :type provider: str :raises: ServiceGatewayNotFound :returns: ServiceGateway """ if provider in self.gateways: return self.gateways[provider] if provider in self.props: props = self.props[provider] else: props = None try: provider_gateway = globals()["%sServiceGateway" % provider]( policy=self.policy_processor, props=props) except: raise #raise ServiceGatewayNotFound(provider) self.gateways[provider] = provider_gateway return provider_gateway def PostObject(self, provider, object_type, payload): """ Request to write a Social Object to a given provider. Requests are verified against the privacy policy, and outgoing objects are sanitised as necessary. :param provider: Provider name :type provider: str :param object_type: Type of object to write :type object_type: str :param payload: Object to post to provider :type payload: Social Object """ headers = SARHeaders("POST", provider, object_type, payload) if not self.privacy_policy: raise Exception("Provide a privacy policy before"\ " making requests.") provider_gateway = self.__getServiceGateway(provider) processor = PolicyProcessor(self.privacy_policy) request_valid = processor._validate_object_request( "POST", provider, object_type, payload) gateway_attr = getattr(provider_gateway, object_type) response = gateway_attr("POST", payload) response_obj = SocialActivityResponse(response, headers) sanitised_response = processor._sanitise_object_request(response_obj)
class SocialObjectsGateway(object): """ This is a friendlier interface to PRISONER's internals, which participation clients should access. This coordinates access to other service gateways, and the management of experimental responses. A single instance of this object should be maintained throughout the lifecycle of an experimental application. """ def __init__(self, server_url=None): self.privacy_policy = None self.exp_design = None # dict keyed on provider names, with values access tokens. this # should be persisted so auth not needed on each # session for the same participant self.keychain = {} # dict keyed on provider names, instances of service gateways self.gateways = {} # maintains a PersistenceManager for DB interaction self.persistence = None self.policy_processor = None self.participant = None # stores instances of SocialObjects under a unique id so clients # can refer to previous instances. TODO: this needs a simple # layer of authentication. is the current user the owner of this # cached object? self.cached_objects = {} # internal cache used to store *unsanitised* # responses to avoid making duplicate network # requests. ONLY for internal use, NEVER directly # access the cache self.internal_cache = {} self.server_url = server_url def cache_object(self, object_to_cache): """ Generates a unique identifier for this object, caches it, and returns the identifier. :param object_to_cache: SocialObject to cache :type object_to_cache: SocialObject :returns: str -- object's identifier """ ident = str(uuid.uuid4()) while(ident in self.cached_objects): ident = str(uuid.uuid4()) object_to_cache.prisoner_id = ident self.cached_objects[ident] = object_to_cache return ident def request_authentication(self, provider, callback): """ Call this if it is necessary to perform authenticated API calls with a service gateway (usually required to write data as a person or to read sensitive data). Each service gateway has its own res mechanism. Calling this will return a token needed to proceed with authentication. Authentication is completed by presenting a relevant interface to users, then calling complete_authentication() with its token. :param provider: Name of provider to authenticate with. :type provider: str. :param callback: URL to let PRISONER authentication server know user has provided authentication :type callback: str. :returns: URL required to complete authentication""" if provider in self.keychain: return None # attempt to find this gateway gateway = self.__getServiceGateway(provider) authent_url = gateway.request_authentication(callback) return authent_url # what url do i need to authetnicate? # let the user consume the authent url and come back in their # own time def register_participant(self, schema, participant): participant= self.persistence.register_participant(schema, participant) self.participant = participant return participant def get_participant(self): return self.participant def complete_authentication(self, provider, request=None): """ Completes the second stage of authentication with a provider. :param provider: Name of provider to authenticate with. :type provider: str. :param request: The request received from the provider when it called the PRISONER callback. This should contain any parameters needed to complete authentication :type request: werkzeug Request """ gateway = self.__getServiceGateway(provider) ret_access_token = gateway.complete_authentication(request) self.persistence.register_participant_with_provider(self.participant[0], provider, ret_access_token) return None def restore_authentication(self, provider, access_token): """ Attempt to provide a service gateway with an existing access token (eg. stored in DB) to authenticate without going through clientside flow. Returns boolean value to indicate success. If False, a call should be made to requst_authentication() to begin clientside flow. :param provider: Name of provider to authenticate with :type provider: str :param access_token: Object used to authenticate with this provider :type access_token: object :returns boolean - was authentication attempt successful? """ gateway = self.__getServiceGateway(provider) auth_success = gateway.restore_authentication(access_token) return auth_success def provide_privacy_policy(self, privacy_policy): """ Provide the privacy policy for this experiment. Used to instantiate an instance of PolicyProcessor. This can only be done once for an instance of SocialObjectGateway. This must be called before attempting to read or write Social Objects. :param privacy_policy: path to a privacy policy XML file :type privacy_policy: str """ if self.privacy_policy: raise Exception("Privacy policy already defined. If \ you need to change it, start a new experiment.") self.privacy_policy = privacy_policy self.policy_processor = PolicyProcessor(self.privacy_policy, self) def provide_experimental_design(self, experimental_design, connection_string): """ Provide the experimental design for this experiment. Used to instantiate a PersistenceManager. Can only be done once per instance of SocialObjectGateway. This must be called before attemtping to persist any response data. :param experimental_design: path to an experimental design XML file :type experimental_design: str :param connection_string: database connection string for persisting data :type connection_string: str """ if self.persistence: raise Exception("Experimental design already defined."+\ "If you need to change it, start a new experiment") self.persistence = PersistenceManager.PersistenceManager(experimental_design, self.policy_processor, connection_string) self.props = self.persistence.props def post_response(self, schema, response): """ Passes the response to the PersistenceManager to write to the internal database. There must be an experimental design bound first. :param schema: Name of the response table to write to :type schema: str. :param response: The response dictionary to write to the specified schema :type response: dict """ if not self.persistence: raise Exception("No experimental design supplied") self.persistence.post_response(schema, response) def GetObjectJSON(self, provider, object_type, payload, criteria, extra_args=None): """ Interface for retrieving objects from a service gateway, for consumption by web services. This differs from GetObject in some fundamental ways. GetObject is more pythonic - you request objects by supplying relevant SocialObjects, and you get SocialObject instances in return. This method however, receives plain-text responses, and returns JSON objects. Whereas GetObject expects a semantically-appropriate SocialObject as the payload (eg. supply an instance of Person to receive objects of a given type owned by that Person), this method expects a payload expressed as a query string, using the namespaced syntax found in the privacy policy spec. For example, a payload of "session:Lastfm.id" will be evaluated as "get objects authored by the user ID in the Last.fm session. "literal:lukeweb", similarly, returns objects owned by that literal user. JSON objects are returned, with the same fields as the Pythonic counterparts. A key difference is that the returned object has an additional attribute injected - prisoner_id. This is a unique identifier for the returned object *that is valid for the duration of this session*. Rather than passing around full instances of objects, subsequent queries, or publication of experimental responses, need only refer to this ID to ensure PRISONER is able to relate your requests back to the full representation of the data. Note that subsequent attempts to retrieve the cached object are subject to the privacy policy sanitisation process of the *original* request. """ # evaluate payload eval_payload = self.policy_processor._infer_object(payload) eval_payload_obj = SocialObjects.SocialObject() eval_payload_obj.id = eval_payload # use this to passthrough original object, not wrapped # as a dumb SocialObject eval_payload_obj = eval_payload # call getobject with cleaned object ret_object = self.GetObject(provider, object_type, eval_payload_obj, True, criteria, extra_args) # cache the object under a unique id, JSONify, return if ret_object != None: ident = self.cache_object(ret_object) try: return jsonpickle.encode(self.cached_objects[ident]) except: return None else: return None def PostObjectJSON(self, provider, object_type, payload): """ Used by web services interface for pushing objects to a service gateway. Expects a payload as a JSON dictionary, where the keys are the appropriate fields of <object_type> This method converts the dictionary to a native object and pushes it through the PRISONER pipe for sanitisation and publication """ dumb_social = SocialObjects.SocialObject() payload = json.loads(payload) for key, value in payload.items(): setattr(dumb_social,key,value) self.PostObject(provider, object_type, dumb_social) def __add_to_cache(self, key, to_cache): """ Make a deep copy of the given object and write it to the internal cache. :param key: Key to cache object under :type key: str :param to_cache: Object to cache :type to_cache: object """ self.internal_cache[key] = copy.deepcopy(to_cache) def GetObject(self, provider, object_type, payload, allow_many=False, criteria = None, extra_args = None): """ Interface for retrieving an object from a service gateway. Requests are verified against the privacy policy, and returned objects are sanitised as appropriate. The payload and filter arguments are semantically distinct. See the documentation for each argument to understand how to use them. :param provider: name of provider to get object from :type provider: str :param object_type: name of object to get :type object_type: str :param payload: This must contain a SocialObject or dictionary of arguments necessary for the ServiceGateway to make a meaningful request. For example, it may be a user ID to retrieve their photos, however it should not contain criteria for filtering the objects returned (see criteria). The expected payload depends on the ServiceGateway and the objects you are requesting. See the documentation for each object exposed by the ServiceGateway to see the payload it requests. :type payload: object :param criteria: Optional criteria for filtering the objects returned by the ServiceGateway. This expression is run on all objects returned by gateway, and only where it evaluates as True is the object returned. Uses syntax similar to lambda expressions, without prefix. x is used to refer to each instance of an object. eg. '"party" in x.tags' :type filter: str :returns: SocialObject -- sanitised for consumption by participation client """ headers = SARHeaders("GET", provider, object_type, payload) if not self.privacy_policy: raise Exception("Provide a privacy policy before"\ " making requests.") provider_gateway = self.__getServiceGateway(provider) processor = self.policy_processor object_type = processor._validate_object_request("GET", provider, object_type, payload) if "%s_%s" % (object_type, payload) not in self.internal_cache: gateway_attr = getattr(provider_gateway,object_type) request_handler = getattr(provider_gateway,"request_handler") response = request_handler(gateway_attr,"GET",payload, extra_args) self.__add_to_cache("%s_%s" % (object_type, payload), response) else: response = self.internal_cache["%s_%s" % (object_type, payload)] sanitised_set = [] if hasattr(response.social_object, "objects"): #is a Collection new_coll = response.social_object if criteria: lambda_func = eval("lambda x: %s" % criteria) response.social_object.objects = filter(lambda_func, response.social_object.objects) for resp in response.social_object.objects: resp.provider = provider response_obj = SocialActivityResponse(resp, headers) sanitised_response = processor._sanitise_object_request(response_obj) sanitised_response.prisoner_id = self.cache_object(sanitised_response) sanitised_set.append(sanitised_response) new_coll.objects = sanitised_set new_coll.prisoner_id = self.cache_object(new_coll) return new_coll else: if criteria: response_set = [response.social_object] response.social_object = filter(eval("lambda x: %s" % criteria), response.social_object.objects) response.provider = provider headers.wrapped_headers = response.headers response_obj = SocialActivityResponse(response.social_object, headers) sanitised_response = processor._sanitise_object_request(response_obj) sanitised_response.prisoner_id = self.cache_object(sanitised_response) sanitised_response.headers = headers return sanitised_response def get_service_gateway(self, provider): """ External wrapper to internal function """ return self.__getServiceGateway(provider) def __getServiceGateway(self, provider): """ Given the name of a provider, return an instance of the appropriate service gateway :param provider: Provider name :type provider: str :raises: ServiceGatewayNotFound :returns: ServiceGateway """ if provider in self.gateways: return self.gateways[provider] if provider in self.props: props = self.props[provider] else: props = None try: provider_gateway = globals()["%sServiceGateway" % provider](policy=self.policy_processor,props=props) except: raise #raise ServiceGatewayNotFound(provider) self.gateways[provider] = provider_gateway return provider_gateway def PostObject(self, provider, object_type, payload): """ Request to write a Social Object to a given provider. Requests are verified against the privacy policy, and outgoing objects are sanitised as necessary. :param provider: Provider name :type provider: str :param object_type: Type of object to write :type object_type: str :param payload: Object to post to provider :type payload: Social Object """ headers = SARHeaders("POST", provider, object_type, payload) if not self.privacy_policy: raise Exception("Provide a privacy policy before"\ " making requests.") provider_gateway = self.__getServiceGateway(provider) processor = PolicyProcessor(self.privacy_policy) request_valid = processor._validate_object_request("POST", provider, object_type, payload) gateway_attr = getattr(provider_gateway, object_type) response = gateway_attr("POST",payload) response_obj = SocialActivityResponse(response, headers) sanitised_response = processor._sanitise_object_request(response_obj)