dotstat_io 0.2.7__py3-none-any.whl → 1.0.1__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 dotstat_io might be problematic. Click here for more details.

@@ -1,70 +1,121 @@
1
1
  import requests
2
+ import os
2
3
  import msal
3
4
  import requests_kerberos
4
5
  import json
5
6
  import time
6
7
 
8
+ from abc import ABC
7
9
  from enum import IntEnum
8
10
  from datetime import datetime
9
11
 
10
-
12
+ #
11
13
  class AuthenticationMode(IntEnum):
12
14
  INTERACTIVE = 1
13
15
  NONINTERACTIVE_WITH_SECRET = 2
14
16
  NONINTERACTIVE_WITH_ADFS = 3
15
17
 
16
- # class to manage ADFS authentication using OIDC flows
17
- class AdfsAuthentication():
18
+ # super class to manage authentication
19
+ class Authentication(ABC):
20
+
21
+ # protected variables
22
+ _access_token = None
23
+ _refresh_token = None
24
+ _creation_time = None
25
+ _expiration_time = None
18
26
 
19
- # Declare constants
20
- __ERROR = "An error occurred: "
21
- __SUCCESS = "Successful authentication"
27
+ # public variables
28
+ init_status = None
22
29
 
23
- # Initialise Adfs_authentication
30
+ # Initialise authentication
24
31
  def __init__(
25
32
  self,
33
+ client_id: str,
26
34
  mode: AuthenticationMode,
27
- client_id: str,
28
- sdmx_resource_id: str,
29
- scopes: list[str] = [],
35
+ client_secret: str = None,
36
+ scopes: list[str] = [],
37
+ token_url: str = None,
38
+ sdmx_resource_id: str = None,
30
39
  authority_url: str = None,
31
- token_url: str = None,
32
40
  redirect_port: int = 3000,
33
- client_secret: str = None
41
+ user: str = None,
42
+ password: str = None,
43
+ proxy: str = None
34
44
  ):
35
- self.__access_token = None
36
-
37
- self.__refresh_token = None
38
- self.__creation_time = None
39
- self.__expiration_time = None
40
-
41
- self.result = None
42
- self.mode = mode
43
- self.client_id = client_id
44
- self.sdmx_resource_id = sdmx_resource_id
45
- self.scopes = scopes
46
- self.authority_url = authority_url
47
- self.token_url = token_url
48
- self.redirect_port = redirect_port
49
- self.client_secret = client_secret
50
-
51
- match self.mode:
52
- case AuthenticationMode.INTERACTIVE:
53
- self.app = msal.PublicClientApplication(
54
- self.client_id, authority=self.authority_url)
55
- case AuthenticationMode.NONINTERACTIVE_WITH_SECRET:
56
- self.app = msal.ConfidentialClientApplication(
57
- self.client_id, authority=self.authority_url, client_credential=self.client_secret)
45
+ self._client_id = client_id
46
+ self._mode = mode
47
+ self._client_secret = client_secret
48
+ self._scopes = scopes
49
+ self._token_url = token_url
50
+ self._sdmx_resource_id = sdmx_resource_id
51
+ self._authority_url = authority_url
52
+ self._redirect_port = redirect_port
53
+ self._user = user
54
+ self._password = password
58
55
 
56
+ if proxy:
57
+ self._proxies = {
58
+ "http": proxy,
59
+ "https": proxy
60
+ }
61
+ else:
62
+ self._proxies = None
63
+
64
+ #
65
+ self._initialize_token()
66
+
67
+ #
59
68
  def __enter__(self):
60
69
  return self
61
70
 
71
+ #
62
72
  def __exit__(self, exc_type, exc_value, traceback):
63
- self.__access_token = None
64
- self.__refresh_token = None
65
- self.__creation_time = None
66
- self.__expiration_time = None
73
+ self._access_token = None
74
+ self._refresh_token = None
75
+ self._creation_time = None
76
+ self._expiration_time = None
77
+ self.init_status = None
67
78
 
79
+ #
80
+ def _initialize_token(self):
81
+ pass
82
+
83
+ #
84
+ def get_token(self):
85
+ if (self._access_token is None) \
86
+ or (datetime.fromtimestamp(self._expiration_time) is not None \
87
+ and datetime.now() >= datetime.fromtimestamp(self._expiration_time)):
88
+ self._initialize_token()
89
+
90
+ return self._access_token
91
+
92
+
93
+ # sub class to manage ADFS authentication using OIDC flows
94
+ class AdfsAuthentication(Authentication):
95
+ # private constants
96
+ __ERROR_OCCURRED = "An error occurred: "
97
+ __SUCCESSFUL_AUTHENTICATION = "Successful authentication"
98
+
99
+ #
100
+ def _initialize_token(self):
101
+ self._access_token = None
102
+ self._refresh_token = None
103
+ self._creation_time = None
104
+ self._expiration_time = None
105
+ self.init_status = None
106
+ match self._mode:
107
+ case AuthenticationMode.INTERACTIVE:
108
+ self._app = msal.PublicClientApplication(
109
+ self._client_id, authority=self._authority_url)
110
+ self.__acquire_token_interactive()
111
+ case AuthenticationMode.NONINTERACTIVE_WITH_SECRET:
112
+ self._app = msal.ConfidentialClientApplication(
113
+ self._client_id, authority=self._authority_url, client_credential=self._client_secret)
114
+ self.__acquire_token_noninteractive_with_secret()
115
+ case AuthenticationMode.NONINTERACTIVE_WITH_ADFS:
116
+ self.__acquire_token_noninteractive_with_adfs()
117
+
118
+ #
68
119
  @classmethod
69
120
  def interactive(
70
121
  cls,
@@ -84,8 +135,9 @@ class AdfsAuthentication():
84
135
  mode=mode
85
136
  )
86
137
 
138
+ #
87
139
  @classmethod
88
- def nointeractive_with_secret(
140
+ def noninteractive_with_secret(
89
141
  cls,
90
142
  client_id: str,
91
143
  sdmx_resource_id: str,
@@ -103,8 +155,9 @@ class AdfsAuthentication():
103
155
  mode=mode
104
156
  )
105
157
 
158
+ #
106
159
  @classmethod
107
- def nointeractive_with_adfs(
160
+ def noninteractive_with_adfs(
108
161
  cls,
109
162
  client_id: str,
110
163
  sdmx_resource_id: str,
@@ -117,173 +170,129 @@ class AdfsAuthentication():
117
170
  token_url=token_url,
118
171
  mode=mode)
119
172
 
120
-
121
- def get_token(self):
122
- self.__access_token = None
123
- self.__refresh_token = None
124
- self.__creation_time = None
125
- self.__expiration_time = None
126
- self.result = None
127
- match self.mode:
128
- case AuthenticationMode.INTERACTIVE:
129
- self._acquire_token_interactive()
130
- case AuthenticationMode.NONINTERACTIVE_WITH_SECRET:
131
- self._acquire_token_noninteractive_with_secret()
132
- case AuthenticationMode.NONINTERACTIVE_WITH_ADFS:
133
- self._acquire_token_noninteractive_with_adfs()
134
-
135
- return self.__access_token
136
-
137
- # Check the token is not expired
138
- def is_access_token_expired(self):
139
- # convert the timestamp to a datetime object
140
- if datetime.fromtimestamp(self.__expiration_time) is not None:
141
- if datetime.now() <= datetime.fromtimestamp(self.__expiration_time):
142
- return False
143
- else:
144
- return True
145
- else:
146
- return True
147
-
148
173
  # Authentication interactively - aka Authorization Code flow
149
- def _acquire_token_interactive(self):
174
+ def __acquire_token_interactive(self):
150
175
  try:
151
-
152
176
  # We now check the cache to see
153
177
  # whether we already have some accounts that the end user already used to sign in before.
154
- accounts = self.app.get_accounts()
178
+ accounts = self._app.get_accounts()
155
179
  if accounts:
156
180
  account = accounts[0]
157
181
  else:
158
182
  account = None
159
183
 
160
184
  # Firstly, looks up a access_token from cache, or using a refresh token
161
- response_silent = self.app.acquire_token_silent(
162
- self.scopes, account=account)
185
+ response_silent = self._app.acquire_token_silent(
186
+ self._scopes, account=account)
163
187
  if not response_silent:
164
188
  # Prompt the user to sign in interactively
165
- response_interactive = self.app.acquire_token_interactive(
166
- scopes=self.scopes, port=self.redirect_port)
189
+ response_interactive = self._app.acquire_token_interactive(
190
+ scopes=self._scopes, port=self._redirect_port)
167
191
  if "access_token" in response_interactive:
168
- self.__access_token = response_interactive.get("access_token")
169
- self.__refresh_token = response_interactive.get("refresh_token")
170
- self.__creation_time = time.time()
171
- self.__expiration_time = time.time() + int(response_interactive.get("expires_in")) - 60 # one minute margin
172
- self.result = self.__SUCCESS
192
+ self._access_token = response_interactive.get("access_token")
193
+ self._refresh_token = response_interactive.get("refresh_token")
194
+ self._creation_time = time.time()
195
+ self._expiration_time = time.time() + int(response_interactive.get("expires_in")) - 60 # one minute margin
196
+ self.init_status = self.__SUCCESSFUL_AUTHENTICATION
173
197
  else:
174
- self.result = f'{self.__ERROR}{response_interactive.get("error")} Error description: {response_interactive.get("error_description")}'
198
+ self.init_status = f'{self.__ERROR_OCCURRED}{response_interactive.get("error")} Error description: {response_interactive.get("error_description")}'
175
199
  else:
176
200
  if "access_token" in response_silent:
177
- self.__access_token = response_silent.get("access_token")
178
- self.__refresh_token = response_silent.get("refresh_token")
179
- self.__creation_time = time.time()
180
- self.__expiration_time = time.time() + int(response_silent.get("expires_in")) - 60 # one minute margin
181
- self.result = self.__SUCCESS
201
+ self._access_token = response_silent.get("access_token")
202
+ self._refresh_token = response_silent.get("refresh_token")
203
+ self._creation_time = time.time()
204
+ self._expiration_time = time.time() + int(response_silent.get("expires_in")) - 60 # one minute margin
205
+ self.init_status = self.__SUCCESSFUL_AUTHENTICATION
182
206
  else:
183
- self.result = f'{self.__ERROR}{response_silent.get("error")} Error description: {response_silent.get("error_description")}'
207
+ self.init_status = f'{self.__ERROR_OCCURRED}{response_silent.get("error")} Error description: {response_silent.get("error_description")}'
184
208
  except Exception as err:
185
- self.result = f'{self.__ERROR}{err}\n'
186
-
209
+ self.init_status = f'{self.__ERROR_OCCURRED}{err}\n'
187
210
 
188
211
  # Authentication non-interactively using any account - aka Client Credentials flow
189
- def _acquire_token_noninteractive_with_secret(self):
212
+ def __acquire_token_noninteractive_with_secret(self):
190
213
  try:
191
- response = self.app.acquire_token_for_client(scopes=self.scopes)
214
+ response = self._app.acquire_token_for_client(scopes=self._scopes)
192
215
  if "access_token" in response:
193
- self.__access_token = response.get("access_token")
194
- self.__creation_time = time.time()
195
- self.__expiration_time = time.time() + int(response.get("expires_in")) - 60 # one minute margin
196
- self.result = self.__SUCCESS
216
+ self._access_token = response.get("access_token")
217
+ self._creation_time = time.time()
218
+ self._expiration_time = time.time() + int(response.get("expires_in")) - 60 # one minute margin
219
+ self.init_status = self.__SUCCESSFUL_AUTHENTICATION
197
220
  else:
198
- self.result = f'{self.__ERROR}{response.get("error")} Error description: {response.get("error_description")}'
221
+ self.init_status = f'{self.__ERROR_OCCURRED}{response.get("error")} Error description: {response.get("error_description")}'
199
222
 
200
223
  except Exception as err:
201
- self.result = f'{self.__ERROR}{err}\n'
202
-
224
+ self.init_status = f'{self.__ERROR_OCCURRED}{err}\n'
203
225
 
204
226
  # Authentication non-interactively using service account - aka Windows Client Authentication
205
- def _acquire_token_noninteractive_with_adfs(self):
227
+ def __acquire_token_noninteractive_with_adfs(self):
206
228
  try:
207
229
  headers = {
208
230
  "Content-Type": "application/x-www-form-urlencoded"
209
231
  }
210
232
 
211
233
  payload = {
212
- 'client_id': self.client_id,
234
+ 'client_id': self._client_id,
213
235
  'use_windows_client_authentication': 'true',
214
236
  'grant_type': 'client_credentials',
215
237
  'scope': 'openid',
216
- 'resource': self.sdmx_resource_id
238
+ 'resource': self._sdmx_resource_id
217
239
  }
218
240
 
219
241
  kerberos_auth = requests_kerberos.HTTPKerberosAuth(
220
242
  mutual_authentication=requests_kerberos.OPTIONAL, force_preemptive=True)
221
- response = requests.post(url=self.token_url, data=payload, auth=kerberos_auth)
222
-
223
- if response.status_code != 200:
224
- message = f'{self.__ERROR} Error code: {response.status_code} Reason: {response.reason}\n'
225
- if len(response.text) > 0:
226
- # Use the json module to load response into a dictionary.
227
- response_dict = json.loads(response.text)
228
- message += f'{self.__ERROR}{response_dict.get("error")} Error description: {response_dict.get("error_description")}\n'
229
-
230
- self.result = message
231
- else:
232
- self.__access_token = response.json()['access_token']
233
- self.__creation_time = time.time()
234
- self.__expiration_time = time.time() + int(response.json()['expires_in']) - 60 # one minute margin
235
- self.result = self.__SUCCESS
236
-
243
+ response = requests.post(url=self._token_url, data=payload, auth=kerberos_auth)
244
+
245
+ # If the response object cannot be converted to json, return an error
246
+ results_json = None
247
+ try:
248
+ results_json = json.loads(response.text)
249
+ if response.status_code == 200:
250
+ self._access_token = results_json['access_token']
251
+ self._creation_time = time.time()
252
+ self._expiration_time = time.time() + int(results_json['expires_in']) - 60 # one minute margin
253
+ self.init_status = self.__SUCCESSFUL_AUTHENTICATION
254
+ else:
255
+ message = f'{self.__ERROR_OCCURRED} Error code: {response.status_code}'
256
+ if len(str(response.reason)) > 0:
257
+ message += os.linesep + 'Reason: ' + str(response.reason) + os.linesep
258
+ if len(response.text) > 0:
259
+ message += f'{self.__ERROR_OCCURRED}{results_json.get("error")} Error description: {results_json.get("error_description")}\n'
260
+
261
+ self.init_status = message
262
+ except ValueError as err:
263
+ self.init_status = self.__ERROR_OCCURRED + os.linesep
264
+ if len(str(response.status_code)) > 0:
265
+ self.init_status += 'Error code: ' + str(response.status_code) + os.linesep
266
+ if len(str(response.reason)) > 0:
267
+ self.init_status += 'Reason: ' + str(response.reason) + os.linesep
268
+ if len(response.text) > 0:
269
+ self.init_status += str(response.text)
270
+ else:
271
+ self.init_status += str(err)
237
272
  except Exception as err:
238
- self.result = f'{self.__ERROR} {err}\n'
239
-
240
-
241
- # class to manage ADFS authentication using OIDC flows
242
- class KeycloakAuthentication():
243
-
244
- # Declare constants
245
- __ERROR = "An error occurred: "
246
- __SUCCESS = "Successful authentication"
247
-
248
- # Initialise Adfs_authentication
249
- def __init__(
250
- self,
251
- mode: AuthenticationMode,
252
- token_url: str,
253
- user: str,
254
- password: str,
255
- client_id: str,
256
- client_secret: str,
257
- proxy: str | None,
258
- scopes: list[str] = []
259
- ):
260
- self.__token = None
261
- self.result = None
262
- self.mode = mode
263
- self.client_id = client_id
264
- self.client_secret = client_secret
265
- self.token_url = token_url
266
- self.user = user
267
- self.password = password
268
- self.scopes = scopes #TODO check if needs explicit adjustment
269
-
270
- if proxy:
271
- self.proxies = {
272
- "http": proxy,
273
- "https": proxy
274
- }
275
- else:
276
- self.proxies = None
277
-
278
-
279
- def __enter__(self):
280
- return self
281
-
282
- def __exit__(self, exc_type, exc_value, traceback):
283
- self.__token = None
273
+ self.init_status = f'{self.__ERROR_OCCURRED} {err}\n'
274
+
275
+
276
+ # sub class to manage Keycloak authentication
277
+ class KeycloakAuthentication(Authentication):
278
+ # private constants
279
+ __ERROR_OCCURRED = "An error occurred: "
280
+ __SUCCESSFUL_AUTHENTICATION = "Successful authentication"
281
+
282
+ #
283
+ def _initialize_token(self):
284
+ self._access_token = None
285
+ self._refresh_token = None
286
+ self._creation_time = None
287
+ self._expiration_time = None
288
+ self.init_status = None
289
+ match self._mode:
290
+ case AuthenticationMode.NONINTERACTIVE_WITH_SECRET:
291
+ self.__acquire_token_noninteractive_with_secret()
284
292
 
293
+ #
285
294
  @classmethod
286
- def nointeractive_with_secret(
295
+ def noninteractive_with_secret(
287
296
  cls,
288
297
  token_url: str,
289
298
  user: str,
@@ -305,31 +314,41 @@ class KeycloakAuthentication():
305
314
  mode=mode
306
315
  )
307
316
 
308
- def get_token(self):
309
- self.__token = None
310
- self.result = None
311
- match self.mode:
312
- case AuthenticationMode.NONINTERACTIVE_WITH_SECRET:
313
- self._acquire_token_noninteractive_with_secret()
317
+ # Authentication non-interactively using any account - aka Client Credentials flow
318
+ def __acquire_token_noninteractive_with_secret(self):
319
+ try:
320
+ payload = {
321
+ 'grant_type': 'password',
322
+ 'client_id': self._client_id,
323
+ 'client_secret': self._client_secret,
324
+ 'username': self._user,
325
+ 'password': self._password
326
+ }
314
327
 
315
- return self.__token
328
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
316
329
 
317
- # Authentication non-interactively using any account - aka Client Credentials flow
318
- def _acquire_token_noninteractive_with_secret(self):
319
- payload = {
320
- 'grant_type': 'password',
321
- 'client_id': self.client_id,
322
- 'client_secret': self.client_secret,
323
- 'username': self.user,
324
- 'password': self.password
325
- }
326
-
327
- headers = {"Content-Type": "application/x-www-form-urlencoded"}
328
-
329
- response = requests.post(self.token_url, proxies=self.proxies, headers=headers, data=payload)
330
- if response.status_code not in {200, 201}:
331
- self.result = f'{self.__ERROR}{response}'
332
- else:
333
- self.__token = response.json()['access_token']
334
- self.result = self.__SUCCESS
335
-
330
+ response = requests.post(self._token_url, proxies=self._proxies, headers=headers, data=payload)
331
+ if response.status_code not in {200, 201}:
332
+ self.init_status = f'{self.__ERROR_OCCURRED}{response}'
333
+ else:
334
+ # If the response object cannot be converted to json, return an error
335
+ results_json = None
336
+ try:
337
+ results_json = json.loads(response.text)
338
+ self._access_token = results_json['access_token']
339
+ self._refresh_token = results_json['refresh_token']
340
+ self._creation_time = time.time()
341
+ self._expiration_time = time.time() + int(results_json['expires_in']) - 60 # one minute margin
342
+ self.init_status = self.__SUCCESSFUL_AUTHENTICATION
343
+ except ValueError as err:
344
+ self.init_status = self.__ERROR_OCCURRED + os.linesep
345
+ if len(str(response.status_code)) > 0:
346
+ self.init_status += 'Error code: ' + str(response.status_code) + os.linesep
347
+ if len(str(response.reason)) > 0:
348
+ self.init_status += 'Reason: ' + str(response.reason) + os.linesep
349
+ if len(response.text) > 0:
350
+ self.init_status += str(response.text)
351
+ else:
352
+ self.init_status += str(err)
353
+ except Exception as err:
354
+ self.init_status = f'{self.__ERROR_OCCURRED}{err}\n'