Skip to content

Commit cdeb608

Browse files
authored
Sync the _shared folder for Communication (Azure#21777)
* identity and network traversal unified with sms * unified get_current_utc_as_int + added a test * adjusted variable name to match the comment * added owners to enforce reviews * token deserialization fix + test
1 parent e9ca031 commit cdeb608

File tree

12 files changed

+153
-118
lines changed

12 files changed

+153
-118
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
/sdk/communication/azure-communication-phonenumbers/ @RoyHerrod @danielav7 @whisper6284 @AlonsoMondal
4343
/sdk/communication/azure-communication-sms/ @RoyHerrod @arifibrahim4
4444
/sdk/communication/azure-communication-identity/ @Azure/acs-identity-sdk
45+
/sdk/communication/**/_shared/ @Azure/acs-identity-sdk
4546

4647
# PRLabel: %KeyVault
4748
/sdk/keyvault/ @schaabs @mccoyp @YalinLi0312

sdk/communication/azure-communication-chat/azure/communication/chat/_shared/utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from msrest.serialization import TZ_UTC
1616
from azure.core.credentials import AccessToken
1717

18-
def _convert_datetime_to_utc_int(expires_on):
18+
def _convert_datetime_to_utc_int(input_datetime):
1919
"""
2020
Converts DateTime in local time to the Epoch in UTC in second.
2121
@@ -24,7 +24,7 @@ def _convert_datetime_to_utc_int(expires_on):
2424
:return: Integer
2525
:rtype: int
2626
"""
27-
return int(calendar.timegm(expires_on.utctimetuple()))
27+
return int(calendar.timegm(input_datetime.utctimetuple()))
2828

2929
def parse_connection_str(conn_str):
3030
# type: (str) -> Tuple[str, str, str, str]
@@ -87,7 +87,7 @@ def create_access_token(token):
8787
padded_base64_payload = base64.b64decode(parts[1] + "==").decode('ascii')
8888
payload = json.loads(padded_base64_payload)
8989
return AccessToken(token,
90-
_convert_datetime_to_utc_int(datetime.fromtimestamp(payload['exp']).replace(tzinfo=TZ_UTC)))
90+
_convert_datetime_to_utc_int(datetime.fromtimestamp(payload['exp'], TZ_UTC)))
9191
except ValueError:
9292
raise ValueError(token_parse_err_msg)
9393

sdk/communication/azure-communication-chat/tests/_shared/test_utils.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
11
import unittest
22
from datetime import datetime
3+
from azure.communication.chat._shared.utils import create_access_token
4+
from azure.communication.chat._shared.utils import get_current_utc_as_int
35
import dateutil.tz
6+
import base64
47

58
from azure.communication.chat._shared.utils import(
69
_convert_datetime_to_utc_int
710
)
811

912
class UtilsTest(unittest.TestCase):
1013

14+
@staticmethod
15+
def get_token_with_custom_expiry(expires_on):
16+
expiry_json = '{"exp": ' + str(expires_on) + '}'
17+
base64expiry = base64.b64encode(
18+
expiry_json.encode('utf-8')).decode('utf-8').rstrip("=")
19+
token_template = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +\
20+
base64expiry + ".adM-ddBZZlQ1WlN3pdPBOF5G4Wh9iZpxNP_fSvpF4cWs"
21+
return token_template
22+
1123
def test_convert_datetime_to_utc_int(self):
1224
# UTC
1325
utc_time_in_sec = _convert_datetime_to_utc_int(datetime(1970, 1, 1, 0, 0, 0, 0, tzinfo=dateutil.tz.tzutc()))
14-
assert utc_time_in_sec == 0
26+
assert utc_time_in_sec == 0
27+
# UTC naive (without a timezone specified)
28+
utc_naive_time_in_sec = _convert_datetime_to_utc_int(datetime(1970, 1, 1, 0, 0, 0, 0))
29+
assert utc_naive_time_in_sec == 0
1530
# PST is UTC-8
1631
pst_time_in_sec = _convert_datetime_to_utc_int(datetime(1970, 1, 1, 0, 0, 0, 0, tzinfo=dateutil.tz.gettz('America/Vancouver')))
1732
assert pst_time_in_sec == 8 * 3600
@@ -22,5 +37,16 @@ def test_convert_datetime_to_utc_int(self):
2237
cst_time_in_sec = _convert_datetime_to_utc_int(datetime(1970, 1, 1, 0, 0, 0, 0, tzinfo=dateutil.tz.gettz('Asia/Shanghai')))
2338
assert cst_time_in_sec == -8 * 3600
2439

40+
41+
def test_access_token_expiry_deserialized_correctly_from_payload(self):
42+
start_timestamp = get_current_utc_as_int()
43+
token_validity_minutes = 60
44+
token_expiry = start_timestamp + token_validity_minutes * 60
45+
46+
token = create_access_token(
47+
self.get_token_with_custom_expiry(token_expiry))
48+
49+
self.assertEqual(token.expires_on, token_expiry)
50+
2551
if __name__ == "__main__":
2652
unittest.main()

sdk/communication/azure-communication-identity/azure/communication/identity/_shared/user_credential.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
# license information.
55
# --------------------------------------------------------------------------
66
from threading import Lock, Condition
7-
from datetime import datetime, timedelta
7+
from datetime import timedelta
88
from typing import ( # pylint: disable=unused-import
99
cast,
1010
Tuple,
1111
)
1212

13-
from msrest.serialization import TZ_UTC
14-
13+
from .utils import get_current_utc_as_int
1514
from .user_token_refresh_options import CommunicationTokenRefreshOptions
1615

16+
1717
class CommunicationTokenCredential(object):
1818
"""Credential type used for authenticating to an Azure Communication service.
1919
:param str token: The token used to authenticate to an Azure Communication service
@@ -24,9 +24,9 @@ class CommunicationTokenCredential(object):
2424
_ON_DEMAND_REFRESHING_INTERVAL_MINUTES = 2
2525

2626
def __init__(self,
27-
token, # type: str
28-
**kwargs
29-
):
27+
token, # type: str
28+
**kwargs
29+
):
3030
token_refresher = kwargs.pop('token_refresher', None)
3131
communication_token_refresh_options = CommunicationTokenRefreshOptions(token=token,
3232
token_refresher=token_refresher)
@@ -35,8 +35,8 @@ def __init__(self,
3535
self._lock = Condition(Lock())
3636
self._some_thread_refreshing = False
3737

38-
def get_token(self):
39-
# type () -> ~azure.core.credentials.AccessToken
38+
def get_token(self, *scopes, **kwargs): # pylint: disable=unused-argument
39+
# type (*str, **Any) -> AccessToken
4040
"""The value of the configured token.
4141
:rtype: ~azure.core.credentials.AccessToken
4242
"""
@@ -79,12 +79,8 @@ def _wait_till_inprogress_thread_finish_refreshing(self):
7979
self._lock.acquire()
8080

8181
def _token_expiring(self):
82-
return self._token.expires_on - self._get_utc_now() <\
83-
timedelta(minutes=self._ON_DEMAND_REFRESHING_INTERVAL_MINUTES)
82+
return self._token.expires_on - get_current_utc_as_int() <\
83+
timedelta(minutes=self._ON_DEMAND_REFRESHING_INTERVAL_MINUTES).total_seconds()
8484

8585
def _is_currenttoken_valid(self):
86-
return self._get_utc_now() < self._token.expires_on
87-
88-
@classmethod
89-
def _get_utc_now(cls):
90-
return datetime.now().replace(tzinfo=TZ_UTC)
86+
return get_current_utc_as_int() < self._token.expires_on

sdk/communication/azure-communication-identity/azure/communication/identity/_shared/user_credential_async.py

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,27 @@
44
# license information.
55
# --------------------------------------------------------------------------
66
from asyncio import Condition, Lock
7-
from datetime import datetime, timedelta
7+
from datetime import timedelta
88
from typing import ( # pylint: disable=unused-import
99
cast,
1010
Tuple,
11+
Any
1112
)
1213

13-
from msrest.serialization import TZ_UTC
14-
14+
from .utils import get_current_utc_as_int
1515
from .user_token_refresh_options import CommunicationTokenRefreshOptions
1616

17+
1718
class CommunicationTokenCredential(object):
1819
"""Credential type used for authenticating to an Azure Communication service.
1920
:param str token: The token used to authenticate to an Azure Communication service
20-
:keyword token_refresher: The token refresher to provide capacity to fetch fresh token
21+
:keyword token_refresher: The async token refresher to provide capacity to fetch fresh token
2122
:raises: TypeError
2223
"""
2324

2425
_ON_DEMAND_REFRESHING_INTERVAL_MINUTES = 2
2526

26-
def __init__(self,
27-
token, # type: str
28-
**kwargs
29-
):
27+
def __init__(self, token: str, **kwargs: Any):
3028
token_refresher = kwargs.pop('token_refresher', None)
3129
communication_token_refresh_options = CommunicationTokenRefreshOptions(token=token,
3230
token_refresher=token_refresher)
@@ -35,25 +33,24 @@ def __init__(self,
3533
self._lock = Condition(Lock())
3634
self._some_thread_refreshing = False
3735

38-
def get_token(self):
39-
# type () -> ~azure.core.credentials.AccessToken
36+
async def get_token(self, *scopes, **kwargs): # pylint: disable=unused-argument
37+
# type (*str, **Any) -> AccessToken
4038
"""The value of the configured token.
4139
:rtype: ~azure.core.credentials.AccessToken
4240
"""
43-
4441
if not self._token_refresher or not self._token_expiring():
4542
return self._token
4643

4744
should_this_thread_refresh = False
4845

49-
with self._lock:
46+
async with self._lock:
5047

5148
while self._token_expiring():
5249
if self._some_thread_refreshing:
5350
if self._is_currenttoken_valid():
5451
return self._token
5552

56-
self._wait_till_inprogress_thread_finish_refreshing()
53+
await self._wait_till_inprogress_thread_finish_refreshing()
5754
else:
5855
should_this_thread_refresh = True
5956
self._some_thread_refreshing = True
@@ -62,32 +59,37 @@ def get_token(self):
6259

6360
if should_this_thread_refresh:
6461
try:
65-
newtoken = self._token_refresher() # pylint:disable=not-callable
62+
newtoken = await self._token_refresher() # pylint:disable=not-callable
6663

67-
with self._lock:
64+
async with self._lock:
6865
self._token = newtoken
6966
self._some_thread_refreshing = False
7067
self._lock.notify_all()
7168
except:
72-
with self._lock:
69+
async with self._lock:
7370
self._some_thread_refreshing = False
7471
self._lock.notify_all()
7572

7673
raise
7774

7875
return self._token
7976

80-
def _wait_till_inprogress_thread_finish_refreshing(self):
77+
async def _wait_till_inprogress_thread_finish_refreshing(self):
8178
self._lock.release()
82-
self._lock.acquire()
79+
await self._lock.acquire()
8380

8481
def _token_expiring(self):
85-
return self._token.expires_on - self._get_utc_now() <\
86-
timedelta(minutes=self._ON_DEMAND_REFRESHING_INTERVAL_MINUTES)
82+
return self._token.expires_on - get_current_utc_as_int() <\
83+
timedelta(minutes=self._ON_DEMAND_REFRESHING_INTERVAL_MINUTES).total_seconds()
8784

8885
def _is_currenttoken_valid(self):
89-
return self._get_utc_now() < self._token.expires_on
86+
return get_current_utc_as_int() < self._token.expires_on
87+
88+
async def close(self) -> None:
89+
pass
90+
91+
async def __aenter__(self):
92+
return self
9093

91-
@classmethod
92-
def _get_utc_now(cls):
93-
return datetime.now().replace(tzinfo=TZ_UTC)
94+
async def __aexit__(self, *args):
95+
await self.close()

sdk/communication/azure-communication-identity/azure/communication/identity/_shared/utils.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import base64
88
import json
9-
import time
109
from typing import ( # pylint: disable=unused-import
1110
cast,
1211
Tuple,
@@ -16,8 +15,16 @@
1615
from msrest.serialization import TZ_UTC
1716
from azure.core.credentials import AccessToken
1817

19-
def _convert_datetime_to_utc_int(expires_on):
20-
return int(calendar.timegm(expires_on.utctimetuple()))
18+
def _convert_datetime_to_utc_int(input_datetime):
19+
"""
20+
Converts DateTime in local time to the Epoch in UTC in second.
21+
22+
:param input_datetime: Input datetime
23+
:type input_datetime: datetime
24+
:return: Integer
25+
:rtype: int
26+
"""
27+
return int(calendar.timegm(input_datetime.utctimetuple()))
2128

2229
def parse_connection_str(conn_str):
2330
# type: (str) -> Tuple[str, str, str, str]
@@ -50,9 +57,10 @@ def get_current_utc_time():
5057
# type: () -> str
5158
return str(datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S ")) + "GMT"
5259

60+
5361
def get_current_utc_as_int():
5462
# type: () -> int
55-
current_utc_datetime = datetime.utcnow().replace(tzinfo=TZ_UTC)
63+
current_utc_datetime = datetime.utcnow()
5664
return _convert_datetime_to_utc_int(current_utc_datetime)
5765

5866
def create_access_token(token):
@@ -79,14 +87,10 @@ def create_access_token(token):
7987
padded_base64_payload = base64.b64decode(parts[1] + "==").decode('ascii')
8088
payload = json.loads(padded_base64_payload)
8189
return AccessToken(token,
82-
_convert_datetime_to_utc_int(datetime.fromtimestamp(payload['exp']).replace(tzinfo=TZ_UTC)))
90+
_convert_datetime_to_utc_int(datetime.fromtimestamp(payload['exp'], TZ_UTC)))
8391
except ValueError:
8492
raise ValueError(token_parse_err_msg)
8593

86-
def _convert_expires_on_datetime_to_utc_int(expires_on):
87-
epoch = time.mktime(datetime(1970, 1, 1).timetuple())
88-
return epoch-time.mktime(expires_on.timetuple())
89-
9094
def get_authentication_policy(
9195
endpoint, # type: str
9296
credential, # type: TokenCredential or str
@@ -122,7 +126,3 @@ def get_authentication_policy(
122126

123127
raise TypeError("Unsupported credential: {}. Use an access token string to use HMACCredentialsPolicy"
124128
"or a token credential from azure.identity".format(type(credential)))
125-
126-
def _convert_expires_on_datetime_to_utc_int(expires_on):
127-
epoch = time.mktime(datetime(1970, 1, 1).timetuple())
128-
return epoch-time.mktime(expires_on.timetuple())

sdk/communication/azure-communication-networktraversal/azure/communication/networktraversal/_shared/policy.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ def __init__(self,
2020
access_key, # type: str
2121
decode_url=False # type: bool
2222
):
23-
# pylint: disable=bad-option-value,useless-object-inheritance,disable=super-with-arguments
2423
# type: (...) -> None
2524
super(HMACCredentialsPolicy, self).__init__()
2625

sdk/communication/azure-communication-networktraversal/azure/communication/networktraversal/_shared/user_credential.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
# license information.
55
# --------------------------------------------------------------------------
66
from threading import Lock, Condition
7-
from datetime import datetime, timedelta
7+
from datetime import timedelta
88
from typing import ( # pylint: disable=unused-import
99
cast,
1010
Tuple,
1111
)
1212

13-
from msrest.serialization import TZ_UTC
14-
13+
from .utils import get_current_utc_as_int
1514
from .user_token_refresh_options import CommunicationTokenRefreshOptions
1615

16+
1717
class CommunicationTokenCredential(object):
1818
"""Credential type used for authenticating to an Azure Communication service.
1919
:param str token: The token used to authenticate to an Azure Communication service
@@ -24,9 +24,9 @@ class CommunicationTokenCredential(object):
2424
_ON_DEMAND_REFRESHING_INTERVAL_MINUTES = 2
2525

2626
def __init__(self,
27-
token, # type: str
28-
**kwargs
29-
):
27+
token, # type: str
28+
**kwargs
29+
):
3030
token_refresher = kwargs.pop('token_refresher', None)
3131
communication_token_refresh_options = CommunicationTokenRefreshOptions(token=token,
3232
token_refresher=token_refresher)
@@ -35,8 +35,8 @@ def __init__(self,
3535
self._lock = Condition(Lock())
3636
self._some_thread_refreshing = False
3737

38-
def get_token(self):
39-
# type () -> ~azure.core.credentials.AccessToken
38+
def get_token(self, *scopes, **kwargs): # pylint: disable=unused-argument
39+
# type (*str, **Any) -> AccessToken
4040
"""The value of the configured token.
4141
:rtype: ~azure.core.credentials.AccessToken
4242
"""
@@ -79,12 +79,8 @@ def _wait_till_inprogress_thread_finish_refreshing(self):
7979
self._lock.acquire()
8080

8181
def _token_expiring(self):
82-
return self._token.expires_on - self._get_utc_now() <\
83-
timedelta(minutes=self._ON_DEMAND_REFRESHING_INTERVAL_MINUTES)
82+
return self._token.expires_on - get_current_utc_as_int() <\
83+
timedelta(minutes=self._ON_DEMAND_REFRESHING_INTERVAL_MINUTES).total_seconds()
8484

8585
def _is_currenttoken_valid(self):
86-
return self._get_utc_now() < self._token.expires_on
87-
88-
@classmethod
89-
def _get_utc_now(cls):
90-
return datetime.now().replace(tzinfo=TZ_UTC)
86+
return get_current_utc_as_int() < self._token.expires_on

0 commit comments

Comments
 (0)