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# 22 23import boto 24from boto.compat import json 25from boto.connection import AWSQueryConnection 26from boto.regioninfo import RegionInfo 27from boto.exception import JSONResponseError 28from boto.cognito.identity import exceptions 29 30 31class CognitoIdentityConnection(AWSQueryConnection): 32 """ 33 Amazon Cognito 34 Amazon Cognito is a web service that delivers scoped temporary 35 credentials to mobile devices and other untrusted environments. 36 Amazon Cognito uniquely identifies a device and supplies the user 37 with a consistent identity over the lifetime of an application. 38 39 Using Amazon Cognito, you can enable authentication with one or 40 more third-party identity providers (Facebook, Google, or Login 41 with Amazon), and you can also choose to support unauthenticated 42 access from your app. Cognito delivers a unique identifier for 43 each user and acts as an OpenID token provider trusted by AWS 44 Security Token Service (STS) to access temporary, limited- 45 privilege AWS credentials. 46 47 To provide end-user credentials, first make an unsigned call to 48 GetId. If the end user is authenticated with one of the supported 49 identity providers, set the `Logins` map with the identity 50 provider token. `GetId` returns a unique identifier for the user. 51 52 Next, make an unsigned call to GetOpenIdToken, which returns the 53 OpenID token necessary to call STS and retrieve AWS credentials. 54 This call expects the same `Logins` map as the `GetId` call, as 55 well as the `IdentityID` originally returned by `GetId`. The token 56 returned by `GetOpenIdToken` can be passed to the STS operation 57 `AssumeRoleWithWebIdentity`_ to retrieve AWS credentials. 58 """ 59 APIVersion = "2014-06-30" 60 DefaultRegionName = "us-east-1" 61 DefaultRegionEndpoint = "cognito-identity.us-east-1.amazonaws.com" 62 ServiceName = "CognitoIdentity" 63 TargetPrefix = "AWSCognitoIdentityService" 64 ResponseError = JSONResponseError 65 66 _faults = { 67 "LimitExceededException": exceptions.LimitExceededException, 68 "ResourceConflictException": exceptions.ResourceConflictException, 69 "DeveloperUserAlreadyRegisteredException": exceptions.DeveloperUserAlreadyRegisteredException, 70 "TooManyRequestsException": exceptions.TooManyRequestsException, 71 "InvalidParameterException": exceptions.InvalidParameterException, 72 "ResourceNotFoundException": exceptions.ResourceNotFoundException, 73 "InternalErrorException": exceptions.InternalErrorException, 74 "NotAuthorizedException": exceptions.NotAuthorizedException, 75 } 76 77 78 def __init__(self, **kwargs): 79 region = kwargs.pop('region', None) 80 if not region: 81 region = RegionInfo(self, self.DefaultRegionName, 82 self.DefaultRegionEndpoint) 83 84 if 'host' not in kwargs or kwargs['host'] is None: 85 kwargs['host'] = region.endpoint 86 87 super(CognitoIdentityConnection, self).__init__(**kwargs) 88 self.region = region 89 90 def _required_auth_capability(self): 91 return ['hmac-v4'] 92 93 def create_identity_pool(self, identity_pool_name, 94 allow_unauthenticated_identities, 95 supported_login_providers=None, 96 developer_provider_name=None, 97 open_id_connect_provider_ar_ns=None): 98 """ 99 Creates a new identity pool. The identity pool is a store of 100 user identity information that is specific to your AWS 101 account. The limit on identity pools is 60 per account. 102 103 :type identity_pool_name: string 104 :param identity_pool_name: A string that you provide. 105 106 :type allow_unauthenticated_identities: boolean 107 :param allow_unauthenticated_identities: TRUE if the identity pool 108 supports unauthenticated logins. 109 110 :type supported_login_providers: map 111 :param supported_login_providers: Optional key:value pairs mapping 112 provider names to provider app IDs. 113 114 :type developer_provider_name: string 115 :param developer_provider_name: The "domain" by which Cognito will 116 refer to your users. This name acts as a placeholder that allows 117 your backend and the Cognito service to communicate about the 118 developer provider. For the `DeveloperProviderName`, you can use 119 letters as well as period ( `.`), underscore ( `_`), and dash ( 120 `-`). 121 Once you have set a developer provider name, you cannot change it. 122 Please take care in setting this parameter. 123 124 :type open_id_connect_provider_ar_ns: list 125 :param open_id_connect_provider_ar_ns: 126 127 """ 128 params = { 129 'IdentityPoolName': identity_pool_name, 130 'AllowUnauthenticatedIdentities': allow_unauthenticated_identities, 131 } 132 if supported_login_providers is not None: 133 params['SupportedLoginProviders'] = supported_login_providers 134 if developer_provider_name is not None: 135 params['DeveloperProviderName'] = developer_provider_name 136 if open_id_connect_provider_ar_ns is not None: 137 params['OpenIdConnectProviderARNs'] = open_id_connect_provider_ar_ns 138 return self.make_request(action='CreateIdentityPool', 139 body=json.dumps(params)) 140 141 def delete_identity_pool(self, identity_pool_id): 142 """ 143 Deletes a user pool. Once a pool is deleted, users will not be 144 able to authenticate with the pool. 145 146 :type identity_pool_id: string 147 :param identity_pool_id: An identity pool ID in the format REGION:GUID. 148 149 """ 150 params = {'IdentityPoolId': identity_pool_id, } 151 return self.make_request(action='DeleteIdentityPool', 152 body=json.dumps(params)) 153 154 def describe_identity_pool(self, identity_pool_id): 155 """ 156 Gets details about a particular identity pool, including the 157 pool name, ID description, creation date, and current number 158 of users. 159 160 :type identity_pool_id: string 161 :param identity_pool_id: An identity pool ID in the format REGION:GUID. 162 163 """ 164 params = {'IdentityPoolId': identity_pool_id, } 165 return self.make_request(action='DescribeIdentityPool', 166 body=json.dumps(params)) 167 168 def get_id(self, account_id, identity_pool_id, logins=None): 169 """ 170 Generates (or retrieves) a Cognito ID. Supplying multiple 171 logins will create an implicit linked account. 172 173 :type account_id: string 174 :param account_id: A standard AWS account ID (9+ digits). 175 176 :type identity_pool_id: string 177 :param identity_pool_id: An identity pool ID in the format REGION:GUID. 178 179 :type logins: map 180 :param logins: A set of optional name-value pairs that map provider 181 names to provider tokens. 182 The available provider names for `Logins` are as follows: 183 184 + Facebook: `graph.facebook.com` 185 + Google: `accounts.google.com` 186 + Amazon: `www.amazon.com` 187 188 """ 189 params = { 190 'AccountId': account_id, 191 'IdentityPoolId': identity_pool_id, 192 } 193 if logins is not None: 194 params['Logins'] = logins 195 return self.make_request(action='GetId', 196 body=json.dumps(params)) 197 198 def get_open_id_token(self, identity_id, logins=None): 199 """ 200 Gets an OpenID token, using a known Cognito ID. This known 201 Cognito ID is returned by GetId. You can optionally add 202 additional logins for the identity. Supplying multiple logins 203 creates an implicit link. 204 205 The OpenId token is valid for 15 minutes. 206 207 :type identity_id: string 208 :param identity_id: A unique identifier in the format REGION:GUID. 209 210 :type logins: map 211 :param logins: A set of optional name-value pairs that map provider 212 names to provider tokens. 213 214 """ 215 params = {'IdentityId': identity_id, } 216 if logins is not None: 217 params['Logins'] = logins 218 return self.make_request(action='GetOpenIdToken', 219 body=json.dumps(params)) 220 221 def get_open_id_token_for_developer_identity(self, identity_pool_id, 222 logins, identity_id=None, 223 token_duration=None): 224 """ 225 Registers (or retrieves) a Cognito `IdentityId` and an OpenID 226 Connect token for a user authenticated by your backend 227 authentication process. Supplying multiple logins will create 228 an implicit linked account. You can only specify one developer 229 provider as part of the `Logins` map, which is linked to the 230 identity pool. The developer provider is the "domain" by which 231 Cognito will refer to your users. 232 233 You can use `GetOpenIdTokenForDeveloperIdentity` to create a 234 new identity and to link new logins (that is, user credentials 235 issued by a public provider or developer provider) to an 236 existing identity. When you want to create a new identity, the 237 `IdentityId` should be null. When you want to associate a new 238 login with an existing authenticated/unauthenticated identity, 239 you can do so by providing the existing `IdentityId`. This API 240 will create the identity in the specified `IdentityPoolId`. 241 242 :type identity_pool_id: string 243 :param identity_pool_id: An identity pool ID in the format REGION:GUID. 244 245 :type identity_id: string 246 :param identity_id: A unique identifier in the format REGION:GUID. 247 248 :type logins: map 249 :param logins: A set of optional name-value pairs that map provider 250 names to provider tokens. Each name-value pair represents a user 251 from a public provider or developer provider. If the user is from a 252 developer provider, the name-value pair will follow the syntax 253 `"developer_provider_name": "developer_user_identifier"`. The 254 developer provider is the "domain" by which Cognito will refer to 255 your users; you provided this domain while creating/updating the 256 identity pool. The developer user identifier is an identifier from 257 your backend that uniquely identifies a user. When you create an 258 identity pool, you can specify the supported logins. 259 260 :type token_duration: long 261 :param token_duration: The expiration time of the token, in seconds. 262 You can specify a custom expiration time for the token so that you 263 can cache it. If you don't provide an expiration time, the token is 264 valid for 15 minutes. You can exchange the token with Amazon STS 265 for temporary AWS credentials, which are valid for a maximum of one 266 hour. The maximum token duration you can set is 24 hours. You 267 should take care in setting the expiration time for a token, as 268 there are significant security implications: an attacker could use 269 a leaked token to access your AWS resources for the token's 270 duration. 271 272 """ 273 params = { 274 'IdentityPoolId': identity_pool_id, 275 'Logins': logins, 276 } 277 if identity_id is not None: 278 params['IdentityId'] = identity_id 279 if token_duration is not None: 280 params['TokenDuration'] = token_duration 281 return self.make_request(action='GetOpenIdTokenForDeveloperIdentity', 282 body=json.dumps(params)) 283 284 def list_identities(self, identity_pool_id, max_results, next_token=None): 285 """ 286 Lists the identities in a pool. 287 288 :type identity_pool_id: string 289 :param identity_pool_id: An identity pool ID in the format REGION:GUID. 290 291 :type max_results: integer 292 :param max_results: The maximum number of identities to return. 293 294 :type next_token: string 295 :param next_token: A pagination token. 296 297 """ 298 params = { 299 'IdentityPoolId': identity_pool_id, 300 'MaxResults': max_results, 301 } 302 if next_token is not None: 303 params['NextToken'] = next_token 304 return self.make_request(action='ListIdentities', 305 body=json.dumps(params)) 306 307 def list_identity_pools(self, max_results, next_token=None): 308 """ 309 Lists all of the Cognito identity pools registered for your 310 account. 311 312 :type max_results: integer 313 :param max_results: The maximum number of identities to return. 314 315 :type next_token: string 316 :param next_token: A pagination token. 317 318 """ 319 params = {'MaxResults': max_results, } 320 if next_token is not None: 321 params['NextToken'] = next_token 322 return self.make_request(action='ListIdentityPools', 323 body=json.dumps(params)) 324 325 def lookup_developer_identity(self, identity_pool_id, identity_id=None, 326 developer_user_identifier=None, 327 max_results=None, next_token=None): 328 """ 329 Retrieves the `IdentityID` associated with a 330 `DeveloperUserIdentifier` or the list of 331 `DeveloperUserIdentifier`s associated with an `IdentityId` for 332 an existing identity. Either `IdentityID` or 333 `DeveloperUserIdentifier` must not be null. If you supply only 334 one of these values, the other value will be searched in the 335 database and returned as a part of the response. If you supply 336 both, `DeveloperUserIdentifier` will be matched against 337 `IdentityID`. If the values are verified against the database, 338 the response returns both values and is the same as the 339 request. Otherwise a `ResourceConflictException` is thrown. 340 341 :type identity_pool_id: string 342 :param identity_pool_id: An identity pool ID in the format REGION:GUID. 343 344 :type identity_id: string 345 :param identity_id: A unique identifier in the format REGION:GUID. 346 347 :type developer_user_identifier: string 348 :param developer_user_identifier: A unique ID used by your backend 349 authentication process to identify a user. Typically, a developer 350 identity provider would issue many developer user identifiers, in 351 keeping with the number of users. 352 353 :type max_results: integer 354 :param max_results: The maximum number of identities to return. 355 356 :type next_token: string 357 :param next_token: A pagination token. The first call you make will 358 have `NextToken` set to null. After that the service will return 359 `NextToken` values as needed. For example, let's say you make a 360 request with `MaxResults` set to 10, and there are 20 matches in 361 the database. The service will return a pagination token as a part 362 of the response. This token can be used to call the API again and 363 get results starting from the 11th match. 364 365 """ 366 params = {'IdentityPoolId': identity_pool_id, } 367 if identity_id is not None: 368 params['IdentityId'] = identity_id 369 if developer_user_identifier is not None: 370 params['DeveloperUserIdentifier'] = developer_user_identifier 371 if max_results is not None: 372 params['MaxResults'] = max_results 373 if next_token is not None: 374 params['NextToken'] = next_token 375 return self.make_request(action='LookupDeveloperIdentity', 376 body=json.dumps(params)) 377 378 def merge_developer_identities(self, source_user_identifier, 379 destination_user_identifier, 380 developer_provider_name, identity_pool_id): 381 """ 382 Merges two users having different `IdentityId`s, existing in 383 the same identity pool, and identified by the same developer 384 provider. You can use this action to request that discrete 385 users be merged and identified as a single user in the Cognito 386 environment. Cognito associates the given source user ( 387 `SourceUserIdentifier`) with the `IdentityId` of the 388 `DestinationUserIdentifier`. Only developer-authenticated 389 users can be merged. If the users to be merged are associated 390 with the same public provider, but as two different users, an 391 exception will be thrown. 392 393 :type source_user_identifier: string 394 :param source_user_identifier: User identifier for the source user. The 395 value should be a `DeveloperUserIdentifier`. 396 397 :type destination_user_identifier: string 398 :param destination_user_identifier: User identifier for the destination 399 user. The value should be a `DeveloperUserIdentifier`. 400 401 :type developer_provider_name: string 402 :param developer_provider_name: The "domain" by which Cognito will 403 refer to your users. This is a (pseudo) domain name that you 404 provide while creating an identity pool. This name acts as a 405 placeholder that allows your backend and the Cognito service to 406 communicate about the developer provider. For the 407 `DeveloperProviderName`, you can use letters as well as period (.), 408 underscore (_), and dash (-). 409 410 :type identity_pool_id: string 411 :param identity_pool_id: An identity pool ID in the format REGION:GUID. 412 413 """ 414 params = { 415 'SourceUserIdentifier': source_user_identifier, 416 'DestinationUserIdentifier': destination_user_identifier, 417 'DeveloperProviderName': developer_provider_name, 418 'IdentityPoolId': identity_pool_id, 419 } 420 return self.make_request(action='MergeDeveloperIdentities', 421 body=json.dumps(params)) 422 423 def unlink_developer_identity(self, identity_id, identity_pool_id, 424 developer_provider_name, 425 developer_user_identifier): 426 """ 427 Unlinks a `DeveloperUserIdentifier` from an existing identity. 428 Unlinked developer users will be considered new identities 429 next time they are seen. If, for a given Cognito identity, you 430 remove all federated identities as well as the developer user 431 identifier, the Cognito identity becomes inaccessible. 432 433 :type identity_id: string 434 :param identity_id: A unique identifier in the format REGION:GUID. 435 436 :type identity_pool_id: string 437 :param identity_pool_id: An identity pool ID in the format REGION:GUID. 438 439 :type developer_provider_name: string 440 :param developer_provider_name: The "domain" by which Cognito will 441 refer to your users. 442 443 :type developer_user_identifier: string 444 :param developer_user_identifier: A unique ID used by your backend 445 authentication process to identify a user. 446 447 """ 448 params = { 449 'IdentityId': identity_id, 450 'IdentityPoolId': identity_pool_id, 451 'DeveloperProviderName': developer_provider_name, 452 'DeveloperUserIdentifier': developer_user_identifier, 453 } 454 return self.make_request(action='UnlinkDeveloperIdentity', 455 body=json.dumps(params)) 456 457 def unlink_identity(self, identity_id, logins, logins_to_remove): 458 """ 459 Unlinks a federated identity from an existing account. 460 Unlinked logins will be considered new identities next time 461 they are seen. Removing the last linked login will make this 462 identity inaccessible. 463 464 :type identity_id: string 465 :param identity_id: A unique identifier in the format REGION:GUID. 466 467 :type logins: map 468 :param logins: A set of optional name-value pairs that map provider 469 names to provider tokens. 470 471 :type logins_to_remove: list 472 :param logins_to_remove: Provider names to unlink from this identity. 473 474 """ 475 params = { 476 'IdentityId': identity_id, 477 'Logins': logins, 478 'LoginsToRemove': logins_to_remove, 479 } 480 return self.make_request(action='UnlinkIdentity', 481 body=json.dumps(params)) 482 483 def update_identity_pool(self, identity_pool_id, identity_pool_name, 484 allow_unauthenticated_identities, 485 supported_login_providers=None, 486 developer_provider_name=None, 487 open_id_connect_provider_ar_ns=None): 488 """ 489 Updates a user pool. 490 491 :type identity_pool_id: string 492 :param identity_pool_id: An identity pool ID in the format REGION:GUID. 493 494 :type identity_pool_name: string 495 :param identity_pool_name: A string that you provide. 496 497 :type allow_unauthenticated_identities: boolean 498 :param allow_unauthenticated_identities: TRUE if the identity pool 499 supports unauthenticated logins. 500 501 :type supported_login_providers: map 502 :param supported_login_providers: Optional key:value pairs mapping 503 provider names to provider app IDs. 504 505 :type developer_provider_name: string 506 :param developer_provider_name: The "domain" by which Cognito will 507 refer to your users. 508 509 :type open_id_connect_provider_ar_ns: list 510 :param open_id_connect_provider_ar_ns: 511 512 """ 513 params = { 514 'IdentityPoolId': identity_pool_id, 515 'IdentityPoolName': identity_pool_name, 516 'AllowUnauthenticatedIdentities': allow_unauthenticated_identities, 517 } 518 if supported_login_providers is not None: 519 params['SupportedLoginProviders'] = supported_login_providers 520 if developer_provider_name is not None: 521 params['DeveloperProviderName'] = developer_provider_name 522 if open_id_connect_provider_ar_ns is not None: 523 params['OpenIdConnectProviderARNs'] = open_id_connect_provider_ar_ns 524 return self.make_request(action='UpdateIdentityPool', 525 body=json.dumps(params)) 526 527 def make_request(self, action, body): 528 headers = { 529 'X-Amz-Target': '%s.%s' % (self.TargetPrefix, action), 530 'Host': self.region.endpoint, 531 'Content-Type': 'application/x-amz-json-1.1', 532 'Content-Length': str(len(body)), 533 } 534 http_request = self.build_base_http_request( 535 method='POST', path='/', auth_path='/', params={}, 536 headers=headers, data=body) 537 response = self._mexe(http_request, sender=None, 538 override_num_retries=10) 539 response_body = response.read().decode('utf-8') 540 boto.log.debug(response_body) 541 if response.status == 200: 542 if response_body: 543 return json.loads(response_body) 544 else: 545 json_body = json.loads(response_body) 546 fault_name = json_body.get('__type', None) 547 exception_class = self._faults.get(fault_name, self.ResponseError) 548 raise exception_class(response.status, response.reason, 549 body=json_body) 550