11# Copyright (c) Microsoft Corporation. All rights reserved.
22# Licensed under the MIT License.
3- from datetime import datetime , timedelta
4- from urllib .parse import urlparse
53
6- from adal import AuthenticationContext
7- import requests
4+ from abc import ABC
85
9- from msrest .authentication import Authentication
10- from .authentication_constants import AuthenticationConstants
6+ from .app_credentials import AppCredentials
7+ from .authenticator import Authenticator
8+ from .credentials_authenticator import CredentialsAuthenticator
119
12- # TODO: Decide to move this to Constants or viceversa (when porting OAuth)
13- AUTH_SETTINGS = {
14- "refreshEndpoint" : "https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token" ,
15- "refreshScope" : "https://api.botframework.com/.default" ,
16- "botConnectorOpenIdMetadata" : "https://login.botframework.com/v1/.well-known/openidconfiguration" ,
17- "botConnectorIssuer" : "https://api.botframework.com" ,
18- "emulatorOpenIdMetadata" : "https://login.microsoftonline.com/botframework.com/v2.0/"
19- ".well-known/openid-configuration" ,
20- "emulatorAuthV31IssuerV1" : "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/" ,
21- "emulatorAuthV31IssuerV2" : "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0" ,
22- "emulatorAuthV32IssuerV1" : "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/" ,
23- "emulatorAuthV32IssuerV2" : "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0" ,
24- }
2510
26-
27- class _OAuthResponse :
28- def __init__ (self ):
29- self .token_type = None
30- self .expires_in = None
31- self .access_token = None
32- self .expiration_time = None
33-
34- @staticmethod
35- def from_json (json_values ):
36- result = _OAuthResponse ()
37- try :
38- result .token_type = json_values ["tokenType" ]
39- result .access_token = json_values ["accessToken" ]
40- result .expires_in = json_values ["expiresIn" ]
41- except KeyError :
42- pass
43- return result
44-
45-
46- class MicrosoftAppCredentials (Authentication ):
11+ class MicrosoftAppCredentials (AppCredentials , ABC ):
4712 """
48- MicrosoftAppCredentials auth implementation and cache .
13+ MicrosoftAppCredentials auth implementation.
4914 """
5015
51- schema = "Bearer"
52-
53- trustedHostNames = {
54- "state.botframework.com" : datetime .max ,
55- "api.botframework.com" : datetime .max ,
56- "token.botframework.com" : datetime .max ,
57- "state.botframework.azure.us" : datetime .max ,
58- "api.botframework.azure.us" : datetime .max ,
59- "token.botframework.azure.us" : datetime .max ,
60- }
61- cache = {}
16+ MICROSOFT_APP_ID = "MicrosoftAppId"
17+ MICROSOFT_PASSWORD = "MicrosoftPassword"
6218
6319 def __init__ (
6420 self ,
@@ -67,120 +23,20 @@ def __init__(
6723 channel_auth_tenant : str = None ,
6824 oauth_scope : str = None ,
6925 ):
70- """
71- Initializes a new instance of MicrosoftAppCredentials class
72- :param app_id: The Microsoft app ID.
73- :param app_password: The Microsoft app password.
74- :param channel_auth_tenant: Optional. The oauth token tenant.
75- """
76- # The configuration property for the Microsoft app ID.
26+ super ().__init__ (
27+ channel_auth_tenant = channel_auth_tenant , oauth_scope = oauth_scope
28+ )
7729 self .microsoft_app_id = app_id
78- # The configuration property for the Microsoft app Password.
7930 self .microsoft_app_password = password
80- tenant = (
81- channel_auth_tenant
82- if channel_auth_tenant
83- else AuthenticationConstants .DEFAULT_CHANNEL_AUTH_TENANT
84- )
85- self .oauth_endpoint = (
86- AuthenticationConstants .TO_CHANNEL_FROM_BOT_LOGIN_URL_PREFIX + tenant
87- )
88- self .oauth_scope = (
89- oauth_scope or AuthenticationConstants .TO_BOT_FROM_CHANNEL_TOKEN_ISSUER
90- )
91- self .token_cache_key = app_id + self .oauth_scope + "-cache" if app_id else None
92- self .authentication_context = AuthenticationContext (self .oauth_endpoint )
9331
94- # pylint: disable=arguments-differ
95- def signed_session (self , session : requests .Session = None ) -> requests .Session :
96- """
97- Gets the signed session.
98- :returns: Signed requests.Session object
32+ def _build_authenticator (self ) -> Authenticator :
9933 """
100- if not session :
101- session = requests .Session ()
102-
103- # If there is no microsoft_app_id and no self.microsoft_app_password, then there shouldn't
104- # be an "Authorization" header on the outgoing activity.
105- if not self .microsoft_app_id and not self .microsoft_app_password :
106- session .headers .pop ("Authorization" , None )
107-
108- else :
109- auth_token = self .get_access_token ()
110- header = "{} {}" .format ("Bearer" , auth_token )
111- session .headers ["Authorization" ] = header
112-
113- return session
114-
115- def get_access_token (self , force_refresh : bool = False ) -> str :
34+ Returns an Authenticator suitable for credential auth.
35+ :return: An Authenticator object
11636 """
117- Gets an OAuth access token.
118- :param force_refresh: True to force a refresh of the token; or false to get
119- a cached token if it exists.
120- :returns: Access token string
121- """
122- if self .microsoft_app_id and self .microsoft_app_password :
123- if not force_refresh :
124- # check the global cache for the token. If we have it, and it's valid, we're done.
125- oauth_token = MicrosoftAppCredentials .cache .get (
126- self .token_cache_key , None
127- )
128- if oauth_token is not None :
129- # we have the token. Is it valid?
130- if oauth_token .expiration_time > datetime .now ():
131- return oauth_token .access_token
132- # We need to refresh the token, because:
133- # 1. The user requested it via the force_refresh parameter
134- # 2. We have it, but it's expired
135- # 3. We don't have it in the cache.
136- oauth_token = self .refresh_token ()
137- MicrosoftAppCredentials .cache .setdefault (self .token_cache_key , oauth_token )
138- return oauth_token .access_token
139- return ""
140-
141- def refresh_token (self ) -> _OAuthResponse :
142- """
143- returns: _OAuthResponse
144- """
145-
146- token = self .authentication_context .acquire_token_with_client_credentials (
147- self .oauth_scope , self .microsoft_app_id , self .microsoft_app_password
148- )
149-
150- oauth_response = _OAuthResponse .from_json (token )
151- oauth_response .expiration_time = datetime .now () + timedelta (
152- seconds = (int (oauth_response .expires_in ) - 300 )
37+ return CredentialsAuthenticator (
38+ app_id = self .microsoft_app_id ,
39+ app_password = self .microsoft_app_password ,
40+ authority = self .oauth_endpoint ,
41+ scope = self .oauth_scope ,
15342 )
154-
155- return oauth_response
156-
157- @staticmethod
158- def trust_service_url (service_url : str , expiration = None ):
159- """
160- Checks if the service url is for a trusted host or not.
161- :param service_url: The service url.
162- :param expiration: The expiration time after which this service url is not trusted anymore.
163- :returns: True if the host of the service url is trusted; False otherwise.
164- """
165- if expiration is None :
166- expiration = datetime .now () + timedelta (days = 1 )
167- host = urlparse (service_url ).hostname
168- if host is not None :
169- MicrosoftAppCredentials .trustedHostNames [host ] = expiration
170-
171- @staticmethod
172- def is_trusted_service (service_url : str ) -> bool :
173- """
174- Checks if the service url is for a trusted host or not.
175- :param service_url: The service url.
176- :returns: True if the host of the service url is trusted; False otherwise.
177- """
178- host = urlparse (service_url ).hostname
179- if host is not None :
180- return MicrosoftAppCredentials ._is_trusted_url (host )
181- return False
182-
183- @staticmethod
184- def _is_trusted_url (host : str ) -> bool :
185- expiration = MicrosoftAppCredentials .trustedHostNames .get (host , datetime .min )
186- return expiration > (datetime .now () - timedelta (minutes = 5 ))
0 commit comments