Beispiel #1
0
    def __init__(self, user=None, key=None):
        """Initialize our client API instance

        Keyword Arguments:
        user        -- User ID to authenticate against
        key         -- Corresponding authentication KEY supplied by Reincubate
        """
        self.user = user if user else settings.get('auth', 'user')
        self.key = key if key else settings.get('auth', 'key')
        self.auth = (self.user, self.key)

        # These are only ever stored between 2FA requests to prevent
        # having to ask the user's details again.
        self.apple_id = None
        self.password = None

        self.session_key = None
        self.devices = {}

        # If 2FA is active on this account, then the trusted device list
        # will be populated once a login is attempted
        self.trusted_devices = []

        # Data from iCloud, populated once logged in and request_data
        # has been called.
        self.data = {}

        self.backup_client = RiCloud._backup_client_class(self)
Beispiel #2
0
    def test_download_everything(self):
        """Can we download all the things?"""
        register_valid_responses()
        api = ICloudApi(user=settings.get('test', 'user'), key=settings.get('test', 'key'))
        api.login(apple_id=settings.get('test', 'apple_id'), password=settings.get('test', 'password'))

        for device_id in api.devices.keys():
            data = api.backup_client.request_data(device_id=device_id)

            assert data is not None

            # Dump the data to our workspace folder for perusal
            filename = '%s.json' % api.devices[device_id]['device_name']
            with open(os.path.join(WORKSPACE_ROOT, filename), 'wb') as out:
                json.dump(data, out, indent=4)

            if len(data['photos']) > 0 and data['photos'][0] != 'Upgrade to view this data.':
                # We have some photos, let's download them too

                for photo in data['photos']:
                    filename = photo['filename']
                    file_id = photo['file_id']

                    with open(os.path.join(WORKSPACE_ROOT, filename), 'wb') as out:
                        api.backup_client.download_file(device_id=device_id, file_id=file_id, out=out)
Beispiel #3
0
    def test_ensure_default_credentials_are_set(self, mock_os_path_expanduser):
        mock_os_path_expanduser.return_value = ''

        settings = get_config()

        assert 'your-ricloud-api-access-token-here' == settings.get('auth', 'token')
        assert 'your-aschannel-stream-name-here' == settings.get('stream', 'stream_endpoint')
Beispiel #4
0
    def test_ricloud_stream_thread_property(self, mock_Thread, mock_Api, mock_Listener, mock_Stream, stream_endpoints):
        """ Are we creating the Stream properly? """
        ricloud_client = RiCloud()

        mock_Stream.assert_called_once_with(
            endpoint=stream_endpoints[0],
            listener=ricloud_client.listener,
            stream=settings.get('stream', 'stream_endpoint'),
            token=settings.get('auth', 'token')
        )
        mock_Stream.return_value.go.assert_called_once_with()
        assert mock_Thread.daemon
        mock_Thread.start.assert_called_once_with()
Beispiel #5
0
    def test_2fa_required(self):
        """What happens when 2FA is enabled?"""
        register_2fa_responses()
        api = ICloudApi(user=settings.get('test', 'user'), key=settings.get('test', 'key'))

        try:
            api.login(apple_id=settings.get('test', 'apple_id'), password=settings.get('test', 'password'))
            raise pytest.skip('2FA is not enabled for this account.')
        except TwoFactorAuthenticationRequired:
            pass

        # The trusted devices fields should now be populated
        assert len(api.trusted_devices) > 0
Beispiel #6
0
    def test_ricloud_stream_thread_property(self, mock_Thread, mock_Api,
                                            mock_Listener, mock_Stream,
                                            stream_endpoints):
        """ Are we creating the Stream properly? """
        ricloud_client = RiCloud()

        mock_Stream.assert_called_once_with(
            endpoint=stream_endpoints[0],
            listener=ricloud_client.listener,
            stream=settings.get('stream', 'stream_endpoint'),
            token=settings.get('auth', 'token'))
        mock_Stream.return_value.go.assert_called_once_with()
        assert mock_Thread.daemon
        mock_Thread.start.assert_called_once_with()
Beispiel #7
0
    def download_file(self, device_id, file_id, out=None):
        """Download an individual file from the iCloud Backup


        Arguments:
        device_id   -- Device ID to pull file from
        file_id     -- File ID representing the file we want to download

        Keyword Arguments:
        out         -- File like object to write response to. If not
                       provided we will write the object to memory.
        """
        if not out:
            out = StringIO()

        post_data = {
            'key': self.api.session_key,
            'device': device_id,
            'file': file_id,
        }

        response = requests.post(settings.get('endpoints', 'download_file'),
                                    auth=self.api.auth, data=post_data,
                                    stream=True, headers=self.api.headers)

        for chunk in response.iter_content(chunk_size=1024):
            if chunk:
                out.write(chunk)
        out.flush()

        return out
def register_2fa_responses():
    responses.add(
                method=responses.POST,
                url=settings.get('endpoints', 'login'),
                body=r"""{
                        "error": "2fa-required",
                        "message": "This account has Two Factor authentication enabled, please select a device to challenge.",
                        "data": {
                            "trustedDevices": ["********02"],
                            "key": "ae354ef8-b7b6-4a40-843f-96dddff3c64f"
                        }
                    }
                """,
                status=409
                )
Beispiel #9
0
    def submit_2fa_challenge(self, code):
        """Submit a user supplied 2FA challenge code"""
        data = {
            'code': code,
            'key': self.session_key,
        }

        response = requests.post(settings.get('endpoints', 'submit_2fa'), auth=self.auth,
                                    data=data, headers=self.headers)

        if response.ok:
            # Retry login
            return self.login(apple_id=self.apple_id, password=self.password)
        else:
            # Unhandled respnose
            response.raise_for_status()
Beispiel #10
0
    def login(self, apple_id, password):
        """Log into the iCloud

        Keyword Arguments:
        apple_id    -- User's apple ID
        password    -- User's apple password
        """
        data = {
            "email": apple_id,
            "password": password,
        }

        if self.session_key:
            data['key'] = self.session_key

        response = requests.post(settings.get('endpoints', 'login'), auth=self.auth,
                                    data=data, headers=self.headers)

        if response.ok:
            # We've logged in successfully
            data = response.json()
            self.session_key = data['key']
            self.devices = data['devices']

            # Clear memory cache of apple credentials
            # These may or may not be set, but better to be on the safe side.
            self.apple_id = None
            self.password = None

        elif response.status_code == 409:
            data = response.json()
            error = data['error']

            if error == '2fa-required':
                # 2fa has been activated on this account
                self.trusted_devices = data['data']['trustedDevices']
                self.session_key = data['data']['key']
                self.apple_id = apple_id
                self.password = password

                raise TwoFactorAuthenticationRequired(
                        'This user has 2FA enabled, please select a device '
                        'and request a challenge.'
                    )
        else:
            # Unhandled response
            response.raise_for_status()
Beispiel #11
0
    def request_2fa_challenge(self, challenge_device):
        """Request a 2FA challenge to the supplied trusted device"""
        data = {
            'challenge': challenge_device,
            'key': self.session_key,
        }

        response = requests.post(settings.get('endpoints', 'challenge_2fa'), auth=self.auth,
                                    data=data, headers=self.headers)

        if response.ok:
            # The challenge has been processed, we now need to wait
            # for the user's submission
            pass
        else:
            # Unhandled respnose
            response.raise_for_status()
Beispiel #12
0
    def request_data(self, device_id, data_mask=None, since=None):
        """Pull data from iCloud from a given point in time.

        Arguments:
        device_id   -- Device ID to pull data for

        Keyword Arguments:
        data_mask   -- Bit Mask representing what data to download
        since       -- Datetime to retrieve data from (i.e. SMS received
                       after this point)
        """
        assert self.api.session_key is not None, 'Session key is required, please log in.'

        if not data_mask:
            # No mask has been set, so use everything
            data_mask = 0
            for mask, _ in BackupClient.AVAILABLE_DATA:
                data_mask |= mask

        if not since:
            since = BackupClient.MIN_REQUEST_DATE

        # The start date cannot be below the min request date, may as
        # well check it now (server will just send an error anyway)
        assert since >= BackupClient.MIN_REQUEST_DATE

        post_data = {
            'key': self.api.session_key,
            'mask': data_mask,
            'since': since.strftime('%Y-%m-%d %H:%M:%S.%f'),
            'device': device_id,
        }

        response = requests.post(settings.get('endpoints', 'download_data'),
                                    auth=self.api.auth, data=post_data,
                                    headers=self.api.headers)

        if not response.ok:
            # Unhandled respnose
            response.raise_for_status()
        return response.json()
def register_valid_responses():
    print settings.get('endpoints', 'login')
    responses.add(
                method=responses.POST,
                url=settings.get('endpoints', 'login'),
                body=r"""
                        {
                            "key": "ae354ef8-b7b6-4a40-843f-96dddff3c64f",
                            "devices": {
                                "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": {
                                    "latest-backup": "2014-06-25 22:19:31.000000",
                                    "model": "N88AP",
                                    "device_name": "John Appleseed's iPhone",
                                    "colour": "white",
                                    "name": "iPhone 3GS"
                                }
                            }
                        }
                    """
                )

    responses.add(
            method=responses.POST,
            url=settings.get('endpoints', 'download_data'),
            body=r"""
                    {
                        "call_history": [
                            {
                                "duration": 0.0,
                                "answered": false,
                                "from_me": false,
                                "date": "2014-10-11 10:16:28.659735",
                                "address": "07123456789"
                            },
                            {
                                "duration": 48.0,
                                "answered": true,
                                "from_me": false,
                                "date": "2014-10-11 10:40:48.496651",
                                "address": null
                            }
                        ],
                        "contacts": [
                             {
                                "records": [
                                    {
                                        "type": "Phone",
                                        "value": "07123 456789"
                                    }
                                ],
                                "first_name": "Test",
                                "last_name": "User"
                            }
                        ],
                        "sms": [
                            {
                                "date": "2014-10-08 00:39:38.000000",
                                "text": "Hi this is a test text",
                                "from_me": false,
                                "number": "+447123456789",
                                "attachments": []
                            },
                            {
                                "date": "2014-10-11 09:58:14.000000",
                                "text": "Your WhatsApp code is 416-741 but you can simply tap on this link to verify your device:\n\nv.whatsapp.com/416741",
                                "from_me": false,
                                "number": "99999",
                                "attachments": []
                            },
                            {
                                "date": "2014-10-11 10:43:50.000000",
                                "text": "Foor and bar",
                                "from_me": true,
                                "number": "+447123456789",
                                "attachments": []
                            },
                            {
                                "date": "2014-10-11 10:52:44.000000",
                                "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur non tortor dolor. Maecenas pretium sapien lorem, nec tristique arcu malesuada eget. Vestibulum at pretium augue. Fusce tempor vehicula cursus. Ut luctus nisi nec neque mattis malesuada. Sed dignissim, lectus maximus fringilla sodales, nunc tortor convallis lectus, vel fermentum augue est et orci. Nulla ut quam dictum, eleifend neque in, dignissim turpis. Duis vel arcu tortor. Nam eget malesuada eros, eu pharetra risus. Nulla facilisi. Aliquam erat volutpat. Nullam porta urna eu lorem consequat, eget tempus turpis sodales. Praesent cursus magna a consectetur venenatis. Phasellus dignissim libero nec purus sodales mattis pellentesque blandit arcu. Quisque neque nisi, viverra et interdum eu, vestibulum non magna. In sed viverra neque. Morbi sollicitudin lacus in elit porta, sollicitudin tincidunt lectus efficitur. Vestibulum venenatis euismod nunc, quis pretium nunc dignissim a. Nam nec dictum libero, vel aliquet risus. Sed aliquet lorem ut libero ultrices dictum.",
                                "from_me": false,
                                "number": "+4471234567890",
                                "attachments": []
                            }
                        ],
                        "photos": [
                            {
                                "filename": "IMG_0001.JPG",
                                "file_id": "343e26971dfe9c395c425c0ccf799df63ae6261e"
                            }
                        ],
                        "browser_history": [
                            {
                                "url": "https://www.google.co.uk/search?q=test&ie=UTF-8&oe=UTF-8&hl=en&client=safari",
                                "last_visit": "2014-10-11 20:00:28.417141",
                                "title": "test - Google Search"
                            },
                            {
                                "url": "http://m.bbc.co.uk/news/",
                                "last_visit": "2014-10-12 09:40:47.248116",
                                "title": "Home - BBC News"
                            }
                        ],
                        "installed_apps": [
                            {
                                "name": "Google Maps",
                                "description": "The Google Maps app for iPhone and iPad makes navigating your world faster and easier. Find the best spots in town and the information you need to get there.\n\n\u2022 Comprehensive, accurate maps in 220 countries and territories\n\u2022 Voice-guided GPS navigation for driving, biking, and walking\n\u2022 Transit directions and maps for over 15,000 cities and towns\n\u2022 Live traffic conditions, incident reports, and automatic rerouting to find the best route\n\u2022 Detailed information on more than 100 million places\n\u2022 Street View and indoor imagery for restaurants, museums, and more\n\n* Some features not available in all countries\n* Continued use of GPS running in the background can dramatically decrease battery life.",
                                "advisory-rating": "12+",
                                "author": "Google, Inc."
                            },
                            {
                                "name": "Spotify Music",
                                "description": "\ufeffSpotify is the best way to listen to music on mobile or tablet. \n\nSearch for any track, artist or album and listen for free. Make and share playlists. Build your biggest, best ever music collection. \n\nGet inspired with personal recommendations, and readymade playlists for just about everything.\n\nListen absolutely free with ads, or get Spotify Premium.\n\nFree on mobile\n\u2022 Play any artist, album, or playlist in shuffle mode.\n\nFree on tablet\n\u2022 Play any song, any time.\n\nPremium features\n\u2022 Play any song, any time on any device: mobile, tablet or computer.\n\u2022 Enjoy ad-free music. \n\u2022 Listen offline. \n\u2022 Get better sound quality.\n\nLove Spotify?\u00a0\nLike us on Facebook: http://www.facebook.com/spotify\u00a0\nFollow us on Twitter: http://twitter.com/spotify",
                                "advisory-rating": "12+",
                                "author": "Spotify Ltd."
                            }
                        ]
                    }
            """
        )

    responses.add(
            method=responses.POST,
            url=settings.get('endpoints', 'download_file'),
            body="I am a file..."
        )
Beispiel #14
0
 def test_log_in(self):
     """Can we log in successfull to the API?"""
     register_valid_responses()
     api = ICloudApi(user=settings.get('test', 'user'), key=settings.get('test', 'key'))
     api.login(apple_id=settings.get('test', 'apple_id'), password=settings.get('test', 'password'))