1# Copyright (c) 2014 Amazon.com, Inc. or its affiliates.  All Rights Reserved
2#
3# Permission is hereby granted, free of charge, to any person obtaining a
4# copy of this software and associated documentation files (the
5# "Software"), to deal in the Software without restriction, including
6# without limitation the rights to use, copy, modify, merge, publish, dis-
7# tribute, sublicense, and/or sell copies of the Software, and to permit
8# persons to whom the Software is furnished to do so, subject to the fol-
9# lowing conditions:
10#
11# The above copyright notice and this permission notice shall be included
12# in all copies or substantial portions of the Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
16# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
17# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20# IN THE SOFTWARE.
21#
22from boto.compat import json
23from boto.exception import JSONResponseError
24from boto.connection import AWSAuthConnection
25from boto.regioninfo import RegionInfo
26from boto.cognito.sync import exceptions
27
28
29class CognitoSyncConnection(AWSAuthConnection):
30    """
31    Amazon Cognito Sync
32    Amazon Cognito Sync provides an AWS service and client library
33    that enable cross-device syncing of application-related user data.
34    High-level client libraries are available for both iOS and
35    Android. You can use these libraries to persist data locally so
36    that it's available even if the device is offline. Developer
37    credentials don't need to be stored on the mobile device to access
38    the service. You can use Amazon Cognito to obtain a normalized
39    user ID and credentials. User data is persisted in a dataset that
40    can store up to 1 MB of key-value pairs, and you can have up to 20
41    datasets per user identity.
42
43    With Amazon Cognito Sync, the data stored for each identity is
44    accessible only to credentials assigned to that identity. In order
45    to use the Cognito Sync service, you need to make API calls using
46    credentials retrieved with `Amazon Cognito Identity service`_.
47    """
48    APIVersion = "2014-06-30"
49    DefaultRegionName = "us-east-1"
50    DefaultRegionEndpoint = "cognito-sync.us-east-1.amazonaws.com"
51    ResponseError = JSONResponseError
52
53    _faults = {
54        "LimitExceededException": exceptions.LimitExceededException,
55        "ResourceConflictException": exceptions.ResourceConflictException,
56        "InvalidConfigurationException": exceptions.InvalidConfigurationException,
57        "TooManyRequestsException": exceptions.TooManyRequestsException,
58        "InvalidParameterException": exceptions.InvalidParameterException,
59        "ResourceNotFoundException": exceptions.ResourceNotFoundException,
60        "InternalErrorException": exceptions.InternalErrorException,
61        "NotAuthorizedException": exceptions.NotAuthorizedException,
62    }
63
64
65    def __init__(self, **kwargs):
66        region = kwargs.get('region')
67        if not region:
68            region = RegionInfo(self, self.DefaultRegionName,
69                                self.DefaultRegionEndpoint)
70        else:
71            del kwargs['region']
72        kwargs['host'] = region.endpoint
73        super(CognitoSyncConnection, self).__init__(**kwargs)
74        self.region = region
75
76    def _required_auth_capability(self):
77        return ['hmac-v4']
78
79    def delete_dataset(self, identity_pool_id, identity_id, dataset_name):
80        """
81        Deletes the specific dataset. The dataset will be deleted
82        permanently, and the action can't be undone. Datasets that
83        this dataset was merged with will no longer report the merge.
84        Any consequent operation on this dataset will result in a
85        ResourceNotFoundException.
86
87        :type identity_pool_id: string
88        :param identity_pool_id: A name-spaced GUID (for example, us-
89            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
90            Cognito. GUID generation is unique within a region.
91
92        :type identity_id: string
93        :param identity_id: A name-spaced GUID (for example, us-
94            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
95            Cognito. GUID generation is unique within a region.
96
97        :type dataset_name: string
98        :param dataset_name: A string of up to 128 characters. Allowed
99            characters are a-z, A-Z, 0-9, '_' (underscore), '-' (dash), and '.'
100            (dot).
101
102        """
103
104        uri = '/identitypools/{0}/identities/{1}/datasets/{2}'.format(
105            identity_pool_id, identity_id, dataset_name)
106        return self.make_request('DELETE', uri, expected_status=200)
107
108    def describe_dataset(self, identity_pool_id, identity_id, dataset_name):
109        """
110        Gets metadata about a dataset by identity and dataset name.
111        The credentials used to make this API call need to have access
112        to the identity data. With Amazon Cognito Sync, each identity
113        has access only to its own data. You should use Amazon Cognito
114        Identity service to retrieve the credentials necessary to make
115        this API call.
116
117        :type identity_pool_id: string
118        :param identity_pool_id: A name-spaced GUID (for example, us-
119            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
120            Cognito. GUID generation is unique within a region.
121
122        :type identity_id: string
123        :param identity_id: A name-spaced GUID (for example, us-
124            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
125            Cognito. GUID generation is unique within a region.
126
127        :type dataset_name: string
128        :param dataset_name: A string of up to 128 characters. Allowed
129            characters are a-z, A-Z, 0-9, '_' (underscore), '-' (dash), and '.'
130            (dot).
131
132        """
133
134        uri = '/identitypools/{0}/identities/{1}/datasets/{2}'.format(
135            identity_pool_id, identity_id, dataset_name)
136        return self.make_request('GET', uri, expected_status=200)
137
138    def describe_identity_pool_usage(self, identity_pool_id):
139        """
140        Gets usage details (for example, data storage) about a
141        particular identity pool.
142
143        :type identity_pool_id: string
144        :param identity_pool_id: A name-spaced GUID (for example, us-
145            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
146            Cognito. GUID generation is unique within a region.
147
148        """
149
150        uri = '/identitypools/{0}'.format(identity_pool_id)
151        return self.make_request('GET', uri, expected_status=200)
152
153    def describe_identity_usage(self, identity_pool_id, identity_id):
154        """
155        Gets usage information for an identity, including number of
156        datasets and data usage.
157
158        :type identity_pool_id: string
159        :param identity_pool_id: A name-spaced GUID (for example, us-
160            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
161            Cognito. GUID generation is unique within a region.
162
163        :type identity_id: string
164        :param identity_id: A name-spaced GUID (for example, us-
165            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
166            Cognito. GUID generation is unique within a region.
167
168        """
169
170        uri = '/identitypools/{0}/identities/{1}'.format(
171            identity_pool_id, identity_id)
172        return self.make_request('GET', uri, expected_status=200)
173
174    def get_identity_pool_configuration(self, identity_pool_id):
175        """
176        Gets the configuration settings of an identity pool.
177
178        :type identity_pool_id: string
179        :param identity_pool_id: A name-spaced GUID (for example, us-
180            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
181            Cognito. This is the ID of the pool for which to return a
182            configuration.
183
184        """
185
186        uri = '/identitypools/{0}/configuration'.format(identity_pool_id)
187        return self.make_request('GET', uri, expected_status=200)
188
189    def list_datasets(self, identity_pool_id, identity_id, next_token=None,
190                      max_results=None):
191        """
192        Lists datasets for an identity. The credentials used to make
193        this API call need to have access to the identity data. With
194        Amazon Cognito Sync, each identity has access only to its own
195        data. You should use Amazon Cognito Identity service to
196        retrieve the credentials necessary to make this API call.
197
198        :type identity_pool_id: string
199        :param identity_pool_id: A name-spaced GUID (for example, us-
200            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
201            Cognito. GUID generation is unique within a region.
202
203        :type identity_id: string
204        :param identity_id: A name-spaced GUID (for example, us-
205            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
206            Cognito. GUID generation is unique within a region.
207
208        :type next_token: string
209        :param next_token: A pagination token for obtaining the next page of
210            results.
211
212        :type max_results: integer
213        :param max_results: The maximum number of results to be returned.
214
215        """
216
217        uri = '/identitypools/{0}/identities/{1}/datasets'.format(
218            identity_pool_id, identity_id)
219        params = {}
220        headers = {}
221        query_params = {}
222        if next_token is not None:
223            query_params['nextToken'] = next_token
224        if max_results is not None:
225            query_params['maxResults'] = max_results
226        return self.make_request('GET', uri, expected_status=200,
227                                 data=json.dumps(params), headers=headers,
228                                 params=query_params)
229
230    def list_identity_pool_usage(self, next_token=None, max_results=None):
231        """
232        Gets a list of identity pools registered with Cognito.
233
234        :type next_token: string
235        :param next_token: A pagination token for obtaining the next page of
236            results.
237
238        :type max_results: integer
239        :param max_results: The maximum number of results to be returned.
240
241        """
242
243        uri = '/identitypools'
244        params = {}
245        headers = {}
246        query_params = {}
247        if next_token is not None:
248            query_params['nextToken'] = next_token
249        if max_results is not None:
250            query_params['maxResults'] = max_results
251        return self.make_request('GET', uri, expected_status=200,
252                                 data=json.dumps(params), headers=headers,
253                                 params=query_params)
254
255    def list_records(self, identity_pool_id, identity_id, dataset_name,
256                     last_sync_count=None, next_token=None, max_results=None,
257                     sync_session_token=None):
258        """
259        Gets paginated records, optionally changed after a particular
260        sync count for a dataset and identity. The credentials used to
261        make this API call need to have access to the identity data.
262        With Amazon Cognito Sync, each identity has access only to its
263        own data. You should use Amazon Cognito Identity service to
264        retrieve the credentials necessary to make this API call.
265
266        :type identity_pool_id: string
267        :param identity_pool_id: A name-spaced GUID (for example, us-
268            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
269            Cognito. GUID generation is unique within a region.
270
271        :type identity_id: string
272        :param identity_id: A name-spaced GUID (for example, us-
273            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
274            Cognito. GUID generation is unique within a region.
275
276        :type dataset_name: string
277        :param dataset_name: A string of up to 128 characters. Allowed
278            characters are a-z, A-Z, 0-9, '_' (underscore), '-' (dash), and '.'
279            (dot).
280
281        :type last_sync_count: long
282        :param last_sync_count: The last server sync count for this record.
283
284        :type next_token: string
285        :param next_token: A pagination token for obtaining the next page of
286            results.
287
288        :type max_results: integer
289        :param max_results: The maximum number of results to be returned.
290
291        :type sync_session_token: string
292        :param sync_session_token: A token containing a session ID, identity
293            ID, and expiration.
294
295        """
296
297        uri = '/identitypools/{0}/identities/{1}/datasets/{2}/records'.format(
298            identity_pool_id, identity_id, dataset_name)
299        params = {}
300        headers = {}
301        query_params = {}
302        if last_sync_count is not None:
303            query_params['lastSyncCount'] = last_sync_count
304        if next_token is not None:
305            query_params['nextToken'] = next_token
306        if max_results is not None:
307            query_params['maxResults'] = max_results
308        if sync_session_token is not None:
309            query_params['syncSessionToken'] = sync_session_token
310        return self.make_request('GET', uri, expected_status=200,
311                                 data=json.dumps(params), headers=headers,
312                                 params=query_params)
313
314    def register_device(self, identity_pool_id, identity_id, platform, token):
315        """
316        Registers a device to receive push sync notifications.
317
318        :type identity_pool_id: string
319        :param identity_pool_id: A name-spaced GUID (for example, us-
320            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
321            Cognito. Here, the ID of the pool that the identity belongs to.
322
323        :type identity_id: string
324        :param identity_id: The unique ID for this identity.
325
326        :type platform: string
327        :param platform: The SNS platform type (e.g. GCM, SDM, APNS,
328            APNS_SANDBOX).
329
330        :type token: string
331        :param token: The push token.
332
333        """
334
335        uri = '/identitypools/{0}/identity/{1}/device'.format(
336            identity_pool_id, identity_id)
337        params = {'Platform': platform, 'Token': token, }
338        headers = {}
339        query_params = {}
340        return self.make_request('POST', uri, expected_status=200,
341                                 data=json.dumps(params), headers=headers,
342                                 params=query_params)
343
344    def set_identity_pool_configuration(self, identity_pool_id,
345                                        push_sync=None):
346        """
347        Sets the necessary configuration for push sync.
348
349        :type identity_pool_id: string
350        :param identity_pool_id: A name-spaced GUID (for example, us-
351            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
352            Cognito. This is the ID of the pool to modify.
353
354        :type push_sync: dict
355        :param push_sync: Configuration options to be applied to the identity
356            pool.
357
358        """
359
360        uri = '/identitypools/{0}/configuration'.format(identity_pool_id)
361        params = {}
362        headers = {}
363        query_params = {}
364        if push_sync is not None:
365            params['PushSync'] = push_sync
366        return self.make_request('POST', uri, expected_status=200,
367                                 data=json.dumps(params), headers=headers,
368                                 params=query_params)
369
370    def subscribe_to_dataset(self, identity_pool_id, identity_id,
371                             dataset_name, device_id):
372        """
373        Subscribes to receive notifications when a dataset is modified
374        by another device.
375
376        :type identity_pool_id: string
377        :param identity_pool_id: A name-spaced GUID (for example, us-
378            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
379            Cognito. The ID of the pool to which the identity belongs.
380
381        :type identity_id: string
382        :param identity_id: Unique ID for this identity.
383
384        :type dataset_name: string
385        :param dataset_name: The name of the dataset to subcribe to.
386
387        :type device_id: string
388        :param device_id: The unique ID generated for this device by Cognito.
389
390        """
391
392        uri = '/identitypools/{0}/identities/{1}/datasets/{2}/subscriptions/{3}'.format(
393            identity_pool_id, identity_id, dataset_name, device_id)
394        return self.make_request('POST', uri, expected_status=200)
395
396    def unsubscribe_from_dataset(self, identity_pool_id, identity_id,
397                                 dataset_name, device_id):
398        """
399        Unsubscribe from receiving notifications when a dataset is
400        modified by another device.
401
402        :type identity_pool_id: string
403        :param identity_pool_id: A name-spaced GUID (for example, us-
404            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
405            Cognito. The ID of the pool to which this identity belongs.
406
407        :type identity_id: string
408        :param identity_id: Unique ID for this identity.
409
410        :type dataset_name: string
411        :param dataset_name: The name of the dataset from which to unsubcribe.
412
413        :type device_id: string
414        :param device_id: The unique ID generated for this device by Cognito.
415
416        """
417
418        uri = '/identitypools/{0}/identities/{1}/datasets/{2}/subscriptions/{3}'.format(
419            identity_pool_id, identity_id, dataset_name, device_id)
420        return self.make_request('DELETE', uri, expected_status=200)
421
422    def update_records(self, identity_pool_id, identity_id, dataset_name,
423                       sync_session_token, device_id=None,
424                       record_patches=None, client_context=None):
425        """
426        Posts updates to records and add and delete records for a
427        dataset and user. The credentials used to make this API call
428        need to have access to the identity data. With Amazon Cognito
429        Sync, each identity has access only to its own data. You
430        should use Amazon Cognito Identity service to retrieve the
431        credentials necessary to make this API call.
432
433        :type identity_pool_id: string
434        :param identity_pool_id: A name-spaced GUID (for example, us-
435            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
436            Cognito. GUID generation is unique within a region.
437
438        :type identity_id: string
439        :param identity_id: A name-spaced GUID (for example, us-
440            east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
441            Cognito. GUID generation is unique within a region.
442
443        :type dataset_name: string
444        :param dataset_name: A string of up to 128 characters. Allowed
445            characters are a-z, A-Z, 0-9, '_' (underscore), '-' (dash), and '.'
446            (dot).
447
448        :type device_id: string
449        :param device_id: The unique ID generated for this device by Cognito.
450
451        :type record_patches: list
452        :param record_patches: A list of patch operations.
453
454        :type sync_session_token: string
455        :param sync_session_token: The SyncSessionToken returned by a previous
456            call to ListRecords for this dataset and identity.
457
458        :type client_context: string
459        :param client_context: Intended to supply a device ID that will
460            populate the `lastModifiedBy` field referenced in other methods.
461            The `ClientContext` field is not yet implemented.
462
463        """
464
465        uri = '/identitypools/{0}/identities/{1}/datasets/{2}'.format(
466            identity_pool_id, identity_id, dataset_name)
467        params = {'SyncSessionToken': sync_session_token, }
468        headers = {}
469        query_params = {}
470        if device_id is not None:
471            params['DeviceId'] = device_id
472        if record_patches is not None:
473            params['RecordPatches'] = record_patches
474        if client_context is not None:
475            headers['x-amz-Client-Context'] = client_context
476        if client_context is not None:
477            headers['x-amz-Client-Context'] = client_context
478        return self.make_request('POST', uri, expected_status=200,
479                                 data=json.dumps(params), headers=headers,
480                                 params=query_params)
481
482    def make_request(self, verb, resource, headers=None, data='',
483                     expected_status=None, params=None):
484        if headers is None:
485            headers = {}
486        response = AWSAuthConnection.make_request(
487            self, verb, resource, headers=headers, data=data, params=params)
488        body = json.loads(response.read().decode('utf-8'))
489        if response.status == expected_status:
490            return body
491        else:
492            error_type = response.getheader('x-amzn-ErrorType').split(':')[0]
493            error_class = self._faults.get(error_type, self.ResponseError)
494            raise error_class(response.status, response.reason, body)
495