diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..20b65c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +output/* +vendor/* +*.pyc +.DS_Store diff --git a/config.py b/config.py new file mode 100644 index 0000000..4dd6158 --- /dev/null +++ b/config.py @@ -0,0 +1,4 @@ +ATTACKER_FB_EMAIL = "EMAIL" +ATTACKER_FB_PASSWORD = "PASSWORD :/" +TARGET_FB_EMAIL = "EMAIL" +TARGET_FB_PASSWORD = "PASSWORD :/" diff --git a/convert.py b/convert.py new file mode 100644 index 0000000..d8f6e85 --- /dev/null +++ b/convert.py @@ -0,0 +1,23 @@ +from utils import _distance + +# [200~40.59.36 73.22.36 73.21.40[201~ +h1 = 73 +m1 = 22 +s1 = 36 + +h2 = 73 +m2 = 21 +s2 = 40 + +hh = 40 +mm = 59 +ss = 36 + + +def conv(h, m, s): + return h + m/60.0 + s/3600.0 + +l1 = (conv(hh, mm, ss), conv(h1, m1, s1)) +l2 = (conv(hh, mm, ss), conv(h2, m2, s2)) + +print _distance(l1, l2) diff --git a/fake.py b/fake.py new file mode 100644 index 0000000..9f362fc --- /dev/null +++ b/fake.py @@ -0,0 +1,44 @@ +from interfaces import * +import utils + +class FakeService: + + def __init__(self): + self.locations = {} + + def move(self, user_id, location): + self.locations[user_id] = location + + def ping(self, pinger_id, pingee_id): + pinger_loc = self.location(pinger_id) + pingee_loc = self.location(pingee_id) + + distance = utils._distance(pinger_loc, pingee_loc) + + return round(distance) + + def location(self, user_id): + loc = self.locations[user_id] + return utils._round_location(loc) + +class FakeUser(User): + + def __init__(self, user_id, initial_location, service): + self._user_id = user_id + self.service = service + + self.move(initial_location) + + def user_id(self): + return self._user_id + + def move(self, co_ords): + self.service.move(self.user_id(), co_ords) + + def ping(self, pingee_id): + return self.service.ping(self.user_id(), pingee_id) + + def location(self): + return self.service.location(self.user_id()) + + diff --git a/interfaces.py b/interfaces.py new file mode 100644 index 0000000..aa0655c --- /dev/null +++ b/interfaces.py @@ -0,0 +1,54 @@ +from utils import * +from pprint import pprint +import random + +class User: + + def move(self, co_ords): + pass + + def ping(self, pingee_id): + pass + +class Attack: + + def run(self): + pass + +class AttackOutput: + + def save_kml(self): + pass + + def location_estimate(self): + pass + +class Property: + + def name(self): + pass + + def description(self): + pass + + def multi_evaluate(self, user1, user2, n_evals, seed=None): + if seed is None: + seed = random.randint(0, 9999) + random.seed(seed) + print("Seed = %d" % seed) + print("Running %s" % self.__class__.__name__) + results = [] + for _ in range(0, n_evals): + inputs = self.generate_inputs() + pprint(inputs) + ret = self.evaluate(user1, user2, inputs) + # pprint(ret) + results.append(ret) + return results + + def generate_inputs(self): + pass + + def evaluate(self, user1, user2, inputs): + pass + diff --git a/kml.py b/kml.py new file mode 100644 index 0000000..78cdc39 --- /dev/null +++ b/kml.py @@ -0,0 +1,62 @@ +import os +import sys +import simplekml +from polycircles import polycircles +import time +import hashlib + +M_IN_MILE = 1609.34 + +parent_folder = os.path.dirname(os.path.realpath(sys.argv[0])) + "/" + + +def points(points, filepath=None): + kml_obj = simplekml.Kml() + + for p in points: + label = p[0] + point = p[1] + p = kml_obj.newpoint( + # name = label, + coords=[(point[1], point[0])]) + # TODO: this is silly + hex_color = hashlib.md5(label).hexdigest()[6:14] + p.style.iconstyle.color = simplekml.Color.hex(hex_color) + + if filepath is None: + filepath = str(time.time()) + + kml_obj.save(parent_folder + "output/" + filepath) + +def save_kml(markers, filepath=None, extra_locations={}): + if filepath is None: + filepath = str(time.time()) + + kml_obj = simplekml.Kml() + for marker in markers: + centre_lat = marker.location[0] + centre_lon = marker.location[1] + + outer_polycircle = polycircles.Polycircle( + latitude=centre_lat, + longitude=centre_lon, + radius=(marker.distance + (marker.precision / 2.0))*M_IN_MILE, + number_of_vertices=50) + inner_polycircle = polycircles.Polycircle( + latitude=centre_lat, + longitude=centre_lon, + radius=(marker.distance - (marker.precision / 2.0))*M_IN_MILE, + number_of_vertices=50) + + pol = kml_obj.newpolygon( + name="%.6f, %.6f | %.3f" % (centre_lat, centre_lon, marker.distance), + outerboundaryis=outer_polycircle.to_kml(), + innerboundaryis=inner_polycircle.to_kml()) + pol.style.polystyle.color = simplekml.Color.changealphaint( + 100, simplekml.Color.red) + + for key, loc in extra_locations.iteritems(): + kml_obj.newpoint( + name=key, + coords=[(loc[1], loc[0])]) + kml_obj.save(parent_folder + "output/" + filepath) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..48b2402 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +pynder +robobrowser +simplekml +polycircles +html5lib diff --git a/run.py b/run.py new file mode 100644 index 0000000..69989b6 --- /dev/null +++ b/run.py @@ -0,0 +1,163 @@ +import time +import setup +import config +import kml +import random +import math +from pprint import pprint + +from tinder import TinderUser +from happn import HappnClient, HappnUser +from fake import * +from utils import * +from interfaces import * + +import utils + + +class DistanceMarkerPatternsParallel(Property): + + INITIAL_LOCATION_DELTA = 0.010 + + def generate_inputs(self): + return { + 'location': (37.749, -122.372), + 'distance_marker_direction': (1, 0), + 'initial_location_direction': (1, 0) + } + + def evaluate(self, attacker, target, inputs): + loc = inputs['location'] + distance_marker_direction = inputs['distance_marker_direction'] + initial_location_direction = inputs['initial_location_direction'] + + n_markers = 6 + + attacker.move(loc) + target.move(loc) + print attacker.location() + print target.location() + print attacker.ping(target.user_id()) + + points = [] + points.append(("TARGET", target.location())) + + def run_f(direction, i): + return utils._distance_markers(attacker, target, n_markers, direction) + + def process_f(distance_marker_set, i): + for m in distance_marker_set.distance_markers: + key = "%.1f" % m.distance + points.append((key, m.location)) + print utils._distance(target.location(), m.location) + kml.points(points, "%s-%d" % (time.time(), i)) + + stream = _axis_pair_test_case_stream(loc, self.INITIAL_LOCATION_DELTA, 6) + + pprint([(tc.attacker_loc, tc.target_loc, tc.direction) for tc in stream]) + + _run_over_stream(attacker, target, run_f, process_f, stream) + + kml.points(points) + +class TestCase: + + def __init__(self, attacker_loc, target_loc, direction): + """ + Attacker starts at attacker_loc + Target starts at target_loc + Attacker shuffles in direction + """ + self.attacker_loc = attacker_loc + self.target_loc = target_loc + self.direction = direction + +def _linear_test_case_stream(centre, line_direction, delta, amp, test_case_direction, target_loc=None): + """ + Return a line of test cases. + """ + def mk(loc): + if target_loc: + tl = target_loc + else: + tl = loc + return TestCase(loc, tl, test_case_direction) + + return map(mk, _linear_location_stream(centre, line_direction, delta, amp)) + +def _linear_location_stream(centre, line_direction, delta, amp): + """ + Return a line of locations either side of given centre. + """ + return [( + centre[0] + line_direction[0] * n * delta, + centre[1] + line_direction[1] * n * delta + ) for n in range(-amp, amp+1)] + +def _square_test_case_stream(centre, delta, amp): + """ + Return a single square of test cases. + """ + dirs = [(-1,0), (1,0), (0,-1), (0,1)] + stream = [] + for d in dirs: + line_dir = (1-abs(d[0]), 1-abs(d[1])) + stream += _linear_test_case_stream(centre, line_dir, delta, amp, d, centre) + return stream + +def _axis_pair_test_case_stream(centre, delta, amp): + """ + Return an axis of test cases that rake across the given centre + """ + c1 = (centre[0] - delta*amp, centre[1]) + c2 = (centre[0], centre[1] - delta*amp) + + line_dir1 = (0, 1) + test_case_dir1 = (1, 0) + line_dir2 = (1, 0) + test_case_dir2 = (0, 1) + + return _linear_test_case_stream(c1, line_dir1, delta, amp, test_case_dir1, centre) + _linear_test_case_stream(c2, line_dir2, delta, amp, test_case_dir2, centre) + + + +def _run_over_stream(attacker, target, run_f, process_f, test_case_stream): + rets = [] + for i, tc in enumerate(test_case_stream): + attacker.move(tc.attacker_loc) + target.move(tc.target_loc) + ret = run_f(tc.direction, i) + processed = process_f(ret, i) + rets.append(processed) + + return rets + + +live = True +attacker = None +target = None +seed = 1234 + +if live: + attacker_session = setup.setup( + config.ATTACKER_FB_EMAIL, + config.ATTACKER_FB_PASSWORD, + ) + target_session = setup.setup( + config.TARGET_FB_EMAIL, + config.TARGET_FB_PASSWORD, + ) + attacker = TinderUser(attacker_session) + target = TinderUser(target_session) +else: + service = FakeService() + attacker = FakeUser(1, (0,0), service) + target = FakeUser(2, (3,5), service) + +print "Attacker ID: %s" % attacker.user_id() +print "Target ID: %s" % target.user_id() + +print(attacker.location()) +prop = DistanceMarkerPatternsParallel() +o = prop.multi_evaluate(attacker, target, 1, seed) +exit(0) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..31e0eb0 --- /dev/null +++ b/setup.py @@ -0,0 +1,70 @@ +import time +import pynder +import robobrowser +import pickle +import re + +import logging_config + +def setup(email, password): + fb_token = _get_fb_token(email, password) + parent_folder = logging_config.parent_folder + + try: + # Read token + access_token_file = open(parent_folder + email + "_access_token.txt", "r") + access_token = access_token_file.read() + access_token_file.close() + session = pynder.Session(access_token) + except Exception as e: + # Update token + access_token = _get_fb_token(email, password) + access_token_file = open(parent_folder + email + "_access_token.txt", "w") + access_token_file.write(access_token) + access_token_file.close() + session = pynder.Session(access_token) + + # Assert setup succeeded + session._api.profile() + + return session + +def _get_fb_token(email, password): + MOBILE_USER_AGENT = "Mozilla/5.0 (Linux; U; en-gb; KFTHWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.16 Safari/535.19" + FB_AUTH_URL = "https://www.facebook.com/v2.6/dialog/oauth?redirect_uri=fb464891386855067%3A%2F%2Fauthorize%2F&display=touch&state=%7B%22challenge%22%3A%22IUUkEUqIGud332lfu%252BMJhxL4Wlc%253D%22%2C%220_auth_logger_id%22%3A%2230F06532-A1B9-4B10-BB28-B29956C71AB1%22%2C%22com.facebook.sdk_client_state%22%3Atrue%2C%223_method%22%3A%22sfvc_auth%22%7D&scope=user_birthday%2Cuser_photos%2Cuser_education_history%2Cemail%2Cuser_relationship_details%2Cuser_friends%2Cuser_work_history%2Cuser_likes&response_type=token%2Csigned_request&default_audience=friends&return_scopes=true&auth_type=rerequest&client_id=464891386855067&ret=login&sdk=ios&logger_id=30F06532-A1B9-4B10-BB28-B29956C71AB1&ext=1470840777&hash=AeZqkIcf-NEW6vBd" + + rb = robobrowser.RoboBrowser(user_agent=MOBILE_USER_AGENT, parser="html5lib") + + # Facebook login + rb.open(FB_AUTH_URL) + login_form = rb.get_form() + login_form["pass"] = password + login_form["email"] = email + rb.submit_form(login_form) + + # Get token + auth_form = rb.get_form() + rb.submit_form(auth_form, submit=auth_form.submit_fields["__CONFIRM__"]) + access_token = re.search(r"access_token=([\w\d]+)", rb.response.content.decode()).groups()[0] + + return access_token + +def _get_happn_fb_token(email, password): + MOBILE_USER_AGENT = "Mozilla/5.0 (Linux; U; en-gb; KFTHWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.16 Safari/535.19" + FB_AUTH_URL = "https://www.facebook.com/v2.9/dialog/oauth?redirect_uri=fb247294518656661%3A%2F%2Fauthorize%2F&state=%7B%22challenge%22%3A%22VS6zY12VAx3qDc%252BvSgWUbxXINPg%253D%22%2C%220_auth_logger_id%22%3A%2212F72F8D-1EBF-477B-AFE3-55C161468659%22%2C%22com.facebook.sdk_client_state%22%3Atrue%2C%223_method%22%3A%22sfvc_auth%22%7D&scope=email%2Cuser_birthday%2Cuser_likes%2Cuser_photos%2Cuser_friends&response_type=token%2Csigned_request&default_audience=friends&return_scopes=true&auth_type=rerequest&client_id=247294518656661&sdk=ios&fbapp_pres=0&sdk_version=4.23.0&_rdr" + + rb = robobrowser.RoboBrowser(user_agent=MOBILE_USER_AGENT, parser="html5lib") + + # Facebook login + rb.open(FB_AUTH_URL) + login_form = rb.get_form() + login_form["pass"] = password + login_form["email"] = email + rb.submit_form(login_form) + + # Get token + auth_form = rb.get_form() + rb.submit_form(auth_form, submit=auth_form.submit_fields["__CONFIRM__"]) + access_token = re.search(r"access_token=([\w\d]+)", rb.response.content.decode()).groups()[0] + + return access_token diff --git a/tinder.py b/tinder.py new file mode 100644 index 0000000..5a0e202 --- /dev/null +++ b/tinder.py @@ -0,0 +1,58 @@ +import time +from interfaces import * + +class TinderUser(User): + + MIN_CO_ORD_DELTA = 0.02 + + def __init__(self, session): + self.session = session + self._user_id = None + + def user_id(self): + if self._user_id is None: + self._user_id = self._fetch_user_id() + + return self._user_id + + def move(self, co_ords): + res = self._with_retries( + lambda: self.session.update_location(co_ords[0]+self.MIN_CO_ORD_DELTA, co_ords[1]+self.MIN_CO_ORD_DELTA)) + assert(200 == res['status']) + + res2 = self._with_retries( + lambda: self.session.update_location(co_ords[0], co_ords[1])) + assert(200 == res['status']) + + def ping(self, pingee_id): + response = self.session._api.user_info(str(pingee_id)) + distance = response['results']["distance_mi"] + if distance is None: + pprint(self.location()) + return distance + + def location(self): + profile = self.profile() + return (profile['pos']['lat'], profile['pos']['lon']) + + def profile(self): + return self.session._api.profile() + + def _fetch_user_id(self): + return self.profile()['_id'] + + def _with_retries(self, f): + ret = None + max_retries = 5 + for i in range(0, max_retries): + try: + ret = f() + break + except Exception as ex: + print("Exception: %s" % ex) + time.sleep(1) + if i == max_retries: + raise + + return ret + diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..b1abf18 --- /dev/null +++ b/utils.py @@ -0,0 +1,156 @@ +import math +import random + +DEFAULT_DPS = 10 + +class DistanceMarker: + + def __init__(self, location, distance, precision): + self.location = location + self.distance = distance + self.precision = precision + +class DistanceMarkerSet: + + def __init__(self, distance_markers, initial_loc=None): + self.distance_markers = distance_markers + self.initial_loc = initial_loc + + def distances(self): + """ + Returns a list of all the distances between successive markers + """ + distances = [] + for i in range(0, len(self.distance_markers) - 1): + cur = self.distance_markers[i] + _next = self.distance_markers[i+1] + + distances.append(_distance(cur.location, _next.location)) + + return distances + + def __str__(self): + return str([(m.location, m.distance, m.precision) for m in self.distance_markers]) + + def to_circles(self): + pass + +def _distance(loc1, loc2): + """ + Returns the Euclidean distance between two co-ordinates in + km + """ + R_EARTH = 6378.1 + + lat1_rad = math.radians(loc1[0]) + lat2_rad = math.radians(loc2[0]) + lon1_rad = math.radians(loc1[1]) + lon2_rad = math.radians(loc2[1]) + delta_lat = lat1_rad - lat2_rad + delta_lon = lon1_rad - lon2_rad + + a = (math.sin(delta_lat/2.0))**2 + (math.cos(lat1_rad)*math.cos(lat2_rad) * (math.sin(delta_lon/2.0))**2) + c = 2 * math.atan2(a**0.5, (1-a)**0.5) + d = R_EARTH * c + + return d + +def _km_to_miles(miles): + MILES_PER_KM = 0.621371 + return miles * MILES_PER_KM + +def _distance_markers(attacker, target, n_distance_markers, direction, target_moves=False): + """ + Given an attacker and a target, shuffle one of them in given direction until + n_distance_marker distance markers have been accumulated + """ + markers = [] + for i in range(0, n_distance_markers): + m = _next_distance_marker(attacker, target, direction, target_moves) + print((m.location, m.distance)) + + markers.append(m) + return DistanceMarkerSet(markers) + +def _next_distance_marker(attacker, target, direction, target_moves=False): + """ + Find the next distance marker between attacker and target + """ + print("Looking for next_distance_marker") + print("ATTACKER: %s" % str(attacker.location())) + print("TARGET: %s" % str(target.location())) + if target_moves: + mover = target + non_mover = attacker + else: + mover = attacker + non_mover = target + + def with_precision(precision): + print("precision: %.10f" % precision) + last_dist = None + pos = mover.location() + + while True: + mover.move(pos) + dist = mover.ping(non_mover.user_id()) + print pos + print mover.location() + print non_mover.location() + print dist + + if dist != last_dist and last_dist is not None: + av_dist = (dist + last_dist) / 2.0 + mover.move(last_pos) + return DistanceMarker(last_pos, av_dist, precision) + + last_dist = dist + last_pos = pos + + pos = ( + pos[0] + direction[0] * precision, + pos[1] + direction[1] * precision + ) + + for dps in range(2, 4): + prec = 10**-dps + m = with_precision(prec) + + mover.move(( + m.location[0] + direction[0] * m.precision, + m.location[1] + direction[1] * m.precision + )) + return m + +LAT_RANGE = (37.5, 37.7) +LON_RANGE = (-122.5, -122.2) + +def _random_location(): + return _round_location((random.uniform(*LAT_RANGE), random.uniform(*LON_RANGE))) + +def _random_direction(): + x = random.uniform(-1, 1) + + y_sign = random.choice([-1, +1]) + y = y_sign * ((1 - x**2) ** 0.5) + + return (x, y) + +def _round_location(loc, dps=DEFAULT_DPS): + return ( + round(loc[0], dps), + round(loc[1], dps) + ) + +def _round(n, round_to): + return round(n / round_to) * round_to + +def _random_displacement(magnitude_range=(0.0, 0.1)): + magnitude = random.uniform(*magnitude_range) + direction = utils._random_direction() + + return ( + magnitude * direction[0], + magnitude * direction[1] + ) +