pypomes-iam 0.6.2__py3-none-any.whl → 0.7.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pypomes-iam might be problematic. Click here for more details.

@@ -10,8 +10,8 @@ from typing import Any
10
10
 
11
11
  from .iam_common import (
12
12
  IamServer, IamParam, UserParam, _iam_lock,
13
- _get_iam_users, _get_iam_registry, # _get_public_key,
14
- _get_login_timeout, _get_user_data
13
+ _get_iam_users, _get_iam_registry, _get_public_key,
14
+ _get_login_timeout, _get_user_data, _iam_server_from_issuer
15
15
  )
16
16
  from .token_pomes import token_get_claims, token_validate
17
17
 
@@ -277,7 +277,7 @@ def action_callback(iam_server: IamServer,
277
277
  def action_exchange(iam_server: IamServer,
278
278
  args: dict[str, Any],
279
279
  errors: list[str] = None,
280
- logger: Logger = None) -> dict[str, Any]:
280
+ logger: Logger = None) -> tuple[str, str]:
281
281
  """
282
282
  Request *iam_server* to issue a token in exchange for the token obtained from another *IAM* server.
283
283
 
@@ -298,17 +298,23 @@ def action_exchange(iam_server: IamServer,
298
298
  :param args: the arguments passed when requesting the service
299
299
  :param errors: incidental errors
300
300
  :param logger: optional logger
301
- :return: the data for the new token, or *None* if error
301
+ :return: a tuple containing the reference user identification and the token obtained, or *None* if error
302
302
  """
303
303
  # initialize the return variable
304
- result: dict[str, Any] | None = None
304
+ result: tuple[str, str] | None = None
305
305
 
306
306
  # obtain the user's identification
307
307
  user_id: str = args.get("user-id") or args.get("login")
308
308
 
309
309
  # obtain the token to be exchanged
310
310
  token: str = args.get("access-token") if user_id else None
311
- if token:
311
+ token_claims: dict[str, dict[str, Any]] = token_get_claims(token=token,
312
+ errors=errors,
313
+ logger=logger) if token else None
314
+ token_issuer: str = _iam_server_from_issuer(issuer=token_claims["payload"]["iss"],
315
+ errors=errors,
316
+ logger=logger) if token_claims else None
317
+ if not errors:
312
318
  # HAZARD: only 'IAM_KEYCLOAK' is currently supported
313
319
  with _iam_lock:
314
320
  # retrieve the IAM server's registry
@@ -324,6 +330,8 @@ def action_exchange(iam_server: IamServer,
324
330
  logger=logger)
325
331
  if not errors:
326
332
  # exchange the token
333
+ if logger:
334
+ logger.debug(msg=f"Requesting the token exchange to IAM server '{iam_server}'")
327
335
  header_data: dict[str, Any] = {
328
336
  "Content-Type": "application/x-www-form-urlencoded"
329
337
  }
@@ -333,7 +341,7 @@ def action_exchange(iam_server: IamServer,
333
341
  "subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
334
342
  "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
335
343
  "audience": registry[IamParam.CLIENT_ID],
336
- "subject_issuer": "oidc"
344
+ "subject_issuer": token_issuer
337
345
  }
338
346
  now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
339
347
  token_data: dict[str, Any] = __post_for_token(iam_server=iam_server,
@@ -368,7 +376,7 @@ def __assert_link(iam_server: IamServer,
368
376
  """
369
377
  Make sure *iam_server* has a link associating *user_id* to an internal user identification.
370
378
  This is a requirement for exchanging a token issued by a federated *IAM* server for an equivalent
371
- one from *iam_server.
379
+ one from *iam_server*.
372
380
 
373
381
  :param iam_server: the reference *IAM* server
374
382
  :param user_id: the reference user identification
@@ -376,6 +384,9 @@ def __assert_link(iam_server: IamServer,
376
384
  :param errors: incidental errors
377
385
  :param logger: optional logger
378
386
  """
387
+ if logger:
388
+ logger.debug(msg="Verifying associations for user "
389
+ f"'{user_id}' in IAM server '{iam_server}'")
379
390
  # obtain a token with administrative rights
380
391
  admin_token: str = __get_administrative_token(iam_server=iam_server,
381
392
  errors=errors,
@@ -384,7 +395,10 @@ def __assert_link(iam_server: IamServer,
384
395
  registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
385
396
  errors=errors,
386
397
  logger=logger)
387
- # obtain the internal user identification for 'from_id'
398
+ # obtain the internal user identification for 'user_id'
399
+ if logger:
400
+ logger.debug(msg="Obtaining internal identification "
401
+ f"for user {user_id} in IAM server {iam_server}")
388
402
  url: str = f"{registry[IamParam.URL_BASE]}/admin/realms/{registry[IamParam.CLIENT_REALM]}/users"
389
403
  header_data: dict[str, str] = {
390
404
  "Authorization": f"Bearer {admin_token}",
@@ -394,15 +408,18 @@ def __assert_link(iam_server: IamServer,
394
408
  "username": user_id,
395
409
  "exact": "true"
396
410
  }
397
- users: dict[str, Any] = __get_for_data(url=url,
398
- header_data=header_data,
399
- params=params,
400
- errors=errors,
401
- logger=logger)
411
+ users: list[dict[str, Any]] = __get_for_data(url=url,
412
+ header_data=header_data,
413
+ params=params,
414
+ errors=errors,
415
+ logger=logger)
402
416
  if users:
403
417
  # verify whether the 'oidc' protocol is referred to in an
404
- # association between 'from_id' and the internal user identification
405
- internal_id: str = users.get("id")
418
+ # association between 'user_id' and the internal user identification
419
+ internal_id: str = users[0].get("id")
420
+ if logger:
421
+ logger.debug(msg="Obtaining the providers federated with "
422
+ f"IAM server '{iam_server}' for internal identification '{internal_id}'")
406
423
  url = (f"{registry[IamParam.URL_BASE]}/admin/realms/"
407
424
  f"{registry[IamParam.CLIENT_REALM]}/users/{internal_id}/federated-identity")
408
425
  providers: list[dict[str, Any]] = __get_for_data(url=url,
@@ -411,27 +428,38 @@ def __assert_link(iam_server: IamServer,
411
428
  errors=errors,
412
429
  logger=logger)
413
430
  no_link: bool = True
414
- for provider in providers:
415
- if provider.get("identityProvider") == "oidc":
416
- no_link = False
417
- break
418
- if no_link:
419
- # link the identities
420
- claims: dict[str, dict[str, Any]] = token_get_claims(token=token,
421
- errors=errors,
422
- logger=logger)
423
- if claims:
424
- token_sub: str = claims["paylod"]["sub"]
425
- url += "/oidc"
426
- body_data: dict[str, Any] = {
427
- "userId": token_sub,
428
- "userName": user_id
429
- }
430
- __post_data(url=url,
431
- header_data=header_data,
432
- body_data=body_data,
433
- errors=errors,
434
- logger=logger)
431
+ claims: dict[str, dict[str, Any]] = token_get_claims(token=token,
432
+ errors=errors,
433
+ logger=logger)
434
+ issuer: str = claims["payload"]["iss"] if claims else None
435
+ provider_name: str = _iam_server_from_issuer(issuer=issuer,
436
+ errors=errors,
437
+ logger=logger) if issuer else None
438
+ if provider_name:
439
+ for provider in providers:
440
+ if provider.get("identityProvider") == provider_name:
441
+ no_link = False
442
+ break
443
+ if no_link:
444
+ # link the identities
445
+ claims: dict[str, dict[str, Any]] = token_get_claims(token=token,
446
+ errors=errors,
447
+ logger=logger)
448
+ if claims:
449
+ token_sub: str = claims["payload"]["sub"]
450
+ if logger:
451
+ logger.debug(msg="Creating an association between identifications "
452
+ f"'{user_id}' and '{token_sub}' in IAM server {iam_server}")
453
+ url += f"/{provider_name}"
454
+ json_data: dict[str, Any] = {
455
+ "userId": token_sub,
456
+ "userName": user_id
457
+ }
458
+ __post_json(url=url,
459
+ header_data=header_data,
460
+ json_data=json_data,
461
+ errors=errors,
462
+ logger=logger)
435
463
 
436
464
 
437
465
  def __get_administrative_token(iam_server: IamServer,
@@ -451,31 +479,47 @@ def __get_administrative_token(iam_server: IamServer,
451
479
  # initialize the return variable
452
480
  result: str | None = None
453
481
 
482
+ if logger:
483
+ logger.debug(msg="Requesting a token with "
484
+ f"administrative rights to IAM Server '{iam_server}'")
485
+
454
486
  # obtain the IAM server's registry
455
487
  registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
456
488
  errors=errors,
457
489
  logger=logger)
458
- if registry and registry[IamParam.ADMIN_ID] and registry[IamParam.ADMIN_SECRET]:
459
- header_data: dict[str, str] = {
460
- "Content-Type": "application/x-www-form-urlencoded"
461
- }
462
- body_data: dict[str, str] = {
463
- "grant-type": "password",
464
- "username": registry[IamParam.ADMIN_ID],
465
- "password": registry[IamParam.ADMIN_SECRET],
466
- "client_id": registry[IamParam.CLIENT_ID]
467
- }
468
- token_data: dict[str, Any] = __post_for_token(iam_server=iam_server,
469
- header_data=header_data,
470
- body_data=body_data,
471
- errors=errors,
472
- logger=logger)
473
- if token_data:
474
- # obtain the token
475
- result = token_data["access_token"]
476
- else:
477
- msg: str = (f"To obtain token with administrative rights from '{iam_server}', "
478
- f"the credentials for the realm administrator must be provided at setup time")
490
+ if registry:
491
+ if registry[IamParam.ADMIN_ID] and registry[IamParam.ADMIN_SECRET]:
492
+ header_data: dict[str, str] = {
493
+ "Content-Type": "application/x-www-form-urlencoded"
494
+ }
495
+ body_data: dict[str, str] = {
496
+ "grant_type": "password",
497
+ "username": registry[IamParam.ADMIN_ID],
498
+ "password": registry[IamParam.ADMIN_SECRET],
499
+ "client_id": "admin-cli"
500
+ }
501
+ token_data: dict[str, Any] = __post_for_token(iam_server=iam_server,
502
+ header_data=header_data,
503
+ body_data=body_data,
504
+ errors=errors,
505
+ logger=logger)
506
+ if token_data:
507
+ # obtain the token
508
+ result = token_data["access_token"]
509
+ if logger:
510
+ logger.debug(msg="Administrative token obtained")
511
+
512
+ elif logger or isinstance(errors, list):
513
+ msg: str = ("Credentials for administrator of realm "
514
+ f"'{registry[IamParam.CLIENT_REALM]}' "
515
+ f"at IAM server '{iam_server}' not provided")
516
+ if logger:
517
+ logger.error(msg=msg)
518
+ if isinstance(errors, list):
519
+ errors.append(msg)
520
+
521
+ elif logger or isinstance(errors, list):
522
+ msg: str = f"Unknown IAM server {iam_server}"
479
523
  if logger:
480
524
  logger.error(msg=msg)
481
525
  if isinstance(errors, list):
@@ -511,14 +555,19 @@ def __get_client_secret(iam_server: IamServer,
511
555
  errors=errors,
512
556
  logger=logger)
513
557
  if token:
558
+ realm: str = registry[IamParam.CLIENT_REALM]
559
+ client_id: str = registry[IamParam.CLIENT_ID]
560
+ if logger:
561
+ logger.debug(msg=f"Obtaining the UUID for client '{client_id}', "
562
+ f"in realm '{realm}' at IAM server '{iam_server}'")
514
563
  # obtain the client UUID
515
- url: str = f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}/clients"
564
+ url: str = f"{registry[IamParam.URL_BASE]}/realms/{realm}/clients"
516
565
  header_data: dict[str, str] = {
517
566
  "Authorization": f"Bearer {token}",
518
567
  "Content-Type": "application/json"
519
568
  }
520
569
  params: dict[str, str] = {
521
- "clientId": registry[IamParam.CLIENT_ID]
570
+ "clientId": client_id
522
571
  }
523
572
  clients: list[dict[str, Any]] = __get_for_data(url=url,
524
573
  header_data=header_data,
@@ -528,6 +577,9 @@ def __get_client_secret(iam_server: IamServer,
528
577
  if clients:
529
578
  # obtain the client's secret password
530
579
  client_uuid: str = clients[0]["id"]
580
+ if logger:
581
+ logger.debug(msg=f"Obtaining the secret for client UUID '{client_uuid}', "
582
+ f"in realm '{realm}' at IAM server '{iam_server}'")
531
583
  url += f"/{client_uuid}/client-secret"
532
584
  reply: dict[str, Any] = __get_for_data(url=url,
533
585
  header_data=header_data,
@@ -594,27 +646,27 @@ def __get_for_data(url: str,
594
646
  return result
595
647
 
596
648
 
597
- def __post_data(url: str,
649
+ def __post_json(url: str,
598
650
  header_data: dict[str, str],
599
- body_data: dict[str, Any],
651
+ json_data: dict[str, Any],
600
652
  errors: list[str] | None,
601
653
  logger: Logger | None) -> None:
602
654
  """
603
655
  Submit a *POST* request to *url*.
604
656
 
605
657
  :param header_data: the data to send in the header of the request
606
- :param body_data: the data to send in the body of the request
658
+ :param json_data: the JSON data to send in the request
607
659
  :param errors: incidental errors
608
660
  :param logger: optional logger
609
661
  """
610
662
  # log the POST
611
663
  if logger:
612
- logger.debug(msg=f"POST {url}, {json.dumps(obj=body_data,
664
+ logger.debug(msg=f"POST {url}, {json.dumps(obj=json_data,
613
665
  ensure_ascii=False)}")
614
666
  try:
615
- response: requests.Response = requests.get(url=url,
616
- headers=header_data,
617
- data=body_data)
667
+ response: requests.Response = requests.post(url=url,
668
+ headers=header_data,
669
+ json=json_data)
618
670
  if response.status_code >= 400:
619
671
  # request failed, report the problem
620
672
  msg = f"POST failure, status {response.status_code}, reason {response.reason}"
@@ -666,9 +718,9 @@ def __post_for_token(iam_server: IamServer,
666
718
  - "username": <realm-administrator-identification>
667
719
  - "password": <realm-administrator-secret>
668
720
 
669
- These attributes are then added to *body_data*:
721
+ These attributes are then added to *body_data*, except for acquiring administrative tokens:
670
722
  - "client_id": <client-id>
671
- - "client_secret": <client-secret> <- except for acquiring administrative tokens
723
+ - "client_secret": <client-secret>
672
724
 
673
725
  If the operation is successful, the token data is stored in the *IAM* server's registry, and returned.
674
726
  Otherwise, *errors* will contain the appropriate error message.
@@ -700,21 +752,25 @@ def __post_for_token(iam_server: IamServer,
700
752
  logger=logger)
701
753
  if registry:
702
754
  # complete the data to send in body of request
703
- body_data["client_id"] = registry[IamParam.CLIENT_ID]
755
+ if body_data["grant_type"] != "password":
756
+ body_data["client_id"] = registry[IamParam.CLIENT_ID]
704
757
 
705
758
  # build the URL
706
759
  base_url: str = f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}"
707
760
  url: str = f"{base_url}/protocol/openid-connect/token"
708
-
709
- # log the POST ('client_secret' data must not be shown in log)
761
+ # 'client_secret' data must not be shown in log
762
+ msg: str = f"POST {url}, {json.dumps(obj=body_data,
763
+ ensure_ascii=False)}"
764
+ if body_data["grant_type"] != "password":
765
+ # 'client_secret' not required for requesting tokens from staging environments
766
+ client_secret: str = __get_client_secret(iam_server=iam_server,
767
+ errors=None,
768
+ logger=logger)
769
+ if client_secret:
770
+ body_data["client_secret"] = client_secret
771
+ # log the POST
710
772
  if logger:
711
- logger.debug(msg=f"POST {url}, {json.dumps(obj=body_data,
712
- ensure_ascii=False)}")
713
- client_secret: str = __get_client_secret(iam_server=iam_server,
714
- errors=errors,
715
- logger=logger)
716
- if body_data["grant_type"] != "password" and client_secret:
717
- body_data["client_secret"] = client_secret
773
+ logger.debug(msg=msg)
718
774
 
719
775
  # obtain the token
720
776
  try:
@@ -797,9 +853,9 @@ def __validate_and_store(iam_server: IamServer,
797
853
  user_data["access-expiration"] = now + token_data.get("expires_in")
798
854
  refresh_exp: int = user_data.get("refresh_expires_in")
799
855
  user_data["refresh-expiration"] = (now + refresh_exp) if refresh_exp else sys.maxsize
800
- # public_key: str = _get_public_key(iam_server=iam_server,
801
- # errors=errors,
802
- # logger=logger)
856
+ public_key: str = _get_public_key(iam_server=iam_server,
857
+ errors=errors,
858
+ logger=logger)
803
859
  recipient_attr = registry[IamParam.RECIPIENT_ATTR]
804
860
  login_id = user_data.pop("login-id", None)
805
861
  base_url: str = f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}"
@@ -807,7 +863,7 @@ def __validate_and_store(iam_server: IamServer,
807
863
  issuer=base_url,
808
864
  recipient_id=login_id,
809
865
  recipient_attr=recipient_attr,
810
- # public_key=public_key,
866
+ public_key=public_key,
811
867
  errors=errors,
812
868
  logger=logger)
813
869
  if claims:
pypomes_iam/iam_pomes.py CHANGED
@@ -40,7 +40,7 @@ def iam_setup(flask_app: Flask,
40
40
  the first time it is needed.
41
41
 
42
42
  :param flask_app: the Flask application
43
- :param iam_server: identifies the supported *IAM* server (*jusbr* or *keycloak*)
43
+ :param iam_server: identifies the supported *IAM* server (currently, *jusbr* or *keycloak*)
44
44
  :param base_url: base URL to request services
45
45
  :param client_id: the client's identification with the *IAM* server
46
46
  :param client_realm: the client realm
@@ -168,7 +168,7 @@ def service_login() -> Response:
168
168
  # methods=["GET"])
169
169
  def service_logout() -> Response:
170
170
  """
171
- Entry point for the JusBR logout service.
171
+ Entry point for the IAM server's logout service.
172
172
 
173
173
  The user is identified by the attribute *user-id* or "login", provided as a request parameter.
174
174
  If successful, remove all data relating to the user from the *IAM* server's registry.
@@ -214,7 +214,7 @@ def service_logout() -> Response:
214
214
  # methods=["POST"])
215
215
  def service_callback() -> Response:
216
216
  """
217
- Entry point for the callback from JusBR on authentication operation.
217
+ Entry point for the callback from the IAM server on authentication operation.
218
218
 
219
219
  This callback is invoked from a front-end application after a successful login at the
220
220
  *IAM* server's login page, forwarding the data received. In a typical OAuth2 flow faction,
@@ -338,13 +338,10 @@ def service_exchange() -> Response:
338
338
  If the exchange is successful, the token data is stored in the *IAM* server's registry, and returned.
339
339
  Otherwise, *errors* will contain the appropriate error message.
340
340
 
341
- On success, the typical *Response* returned will contain the following attributes:
341
+ On success, the returned *Response* will contain the following JSON:
342
342
  {
343
- "token_type": "Bearer",
344
- "access_token": <str>,
345
- "expires_in": <number-of-seconds>,
346
- "refresh_token": <str>,
347
- "refesh_expires_in": <number-of-seconds>
343
+ "user-id": <reference-user-identification>,
344
+ "access-token": <token>
348
345
  }
349
346
 
350
347
  :return: *Response* containing the token data, or *BAD REQUEST*
@@ -360,10 +357,10 @@ def service_exchange() -> Response:
360
357
  errors=errors,
361
358
  logger=__IAM_LOGGER)
362
359
  # exchange the token
363
- token_data: dict[str, Any] | None = None
360
+ token_info: tuple[str, str] | None = None
364
361
  if iam_server:
365
362
  errors: list[str] = []
366
- token_data = action_exchange(iam_server=iam_server,
363
+ token_info = action_exchange(iam_server=iam_server,
367
364
  args=request.args,
368
365
  errors=errors,
369
366
  logger=__IAM_LOGGER)
@@ -372,7 +369,8 @@ def service_exchange() -> Response:
372
369
  result = Response(response="; ".join(errors),
373
370
  status=400)
374
371
  else:
375
- result = jsonify(token_data)
372
+ result = jsonify({"user-id": token_info[0],
373
+ "access-token": token_info[1]})
376
374
 
377
375
  # log the response
378
376
  if __IAM_LOGGER:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypomes_iam
3
- Version: 0.6.2
3
+ Version: 0.7.4
4
4
  Summary: A collection of Python pomes, penyeach (IAM modules)
5
5
  Project-URL: Homepage, https://github.com/TheWiseCoder/PyPomes-IAM
6
6
  Project-URL: Bug Tracker, https://github.com/TheWiseCoder/PyPomes-IAM/issues
@@ -0,0 +1,11 @@
1
+ pypomes_iam/__init__.py,sha256=_6tSFfjuU-5p6TAMqNLHSL6IQmaJMSYuEW-TG3ybhTI,1044
2
+ pypomes_iam/iam_actions.py,sha256=8cHtE6AU0sh8IBfO5w1IQj3HVZBSuTFMrrcOK0bnF9E,42774
3
+ pypomes_iam/iam_common.py,sha256=ki_-m6fqJqUbGjgTD41r9zaE-FOXgA_c_tLisIYYTfU,15457
4
+ pypomes_iam/iam_pomes.py,sha256=_kLnrZG25XhJsIv3wqDl_2sIJ2ho_2TIMKrPCyPmA7Q,7362
5
+ pypomes_iam/iam_services.py,sha256=AzrZux2Pt_FoCNcTcXfWphHb587vB3WIbKYG7RFf5zE,15821
6
+ pypomes_iam/provider_pomes.py,sha256=3mMj5LQs53YEINUEOfFBAxOwOP3aOR_szlE4daEBLK0,10523
7
+ pypomes_iam/token_pomes.py,sha256=K4nSAotKUoHIE2s3ltc_nVimlNeKS9tnD-IlslkAvkk,6626
8
+ pypomes_iam-0.7.4.dist-info/METADATA,sha256=Si9dT8ORriAGUwCTrl4jYs3tsoz0XVgHATeqIVPv96g,661
9
+ pypomes_iam-0.7.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ pypomes_iam-0.7.4.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
11
+ pypomes_iam-0.7.4.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- pypomes_iam/__init__.py,sha256=_6tSFfjuU-5p6TAMqNLHSL6IQmaJMSYuEW-TG3ybhTI,1044
2
- pypomes_iam/iam_actions.py,sha256=DhDZcY6j3uSWnm5Q5SrA5jriTUCatLqJKd9K7IjA2i8,39283
3
- pypomes_iam/iam_common.py,sha256=ki_-m6fqJqUbGjgTD41r9zaE-FOXgA_c_tLisIYYTfU,15457
4
- pypomes_iam/iam_pomes.py,sha256=XkxpwwGivUR3Y1TKR6McrqLUnpFJhRvIrsEn9T_Ut9A,7351
5
- pypomes_iam/iam_services.py,sha256=IkCjrKDX1Ix7BiHh-BL3VKz5xogcNC8prXkHyJzQoZ8,15862
6
- pypomes_iam/provider_pomes.py,sha256=3mMj5LQs53YEINUEOfFBAxOwOP3aOR_szlE4daEBLK0,10523
7
- pypomes_iam/token_pomes.py,sha256=K4nSAotKUoHIE2s3ltc_nVimlNeKS9tnD-IlslkAvkk,6626
8
- pypomes_iam-0.6.2.dist-info/METADATA,sha256=XeUm7yxjoWFURVvoHDSoYkF5iROCC696PPuHydJ1UIs,661
9
- pypomes_iam-0.6.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- pypomes_iam-0.6.2.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
11
- pypomes_iam-0.6.2.dist-info/RECORD,,