dotstat_io 0.2.7__py3-none-any.whl → 1.0.0__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 constants
22
+ _ERROR_OCCURRED = "An error occurred: "
23
+ _SUCCESS = "Successful authentication"
24
+
25
+ # protected variables
26
+ _access_token = None
27
+ _refresh_token = None
28
+ _creation_time = None
29
+ _expiration_time = None
18
30
 
19
- # Declare constants
20
- __ERROR = "An error occurred: "
21
- __SUCCESS = "Successful authentication"
31
+ # public variables
32
+ init_status = None
22
33
 
23
- # Initialise Adfs_authentication
34
+ # Initialise authentication
24
35
  def __init__(
25
36
  self,
37
+ client_id: str,
26
38
  mode: AuthenticationMode,
27
- client_id: str,
28
- sdmx_resource_id: str,
29
- scopes: list[str] = [],
39
+ client_secret: str = None,
40
+ scopes: list[str] = [],
41
+ token_url: str = None,
42
+ sdmx_resource_id: str = None,
30
43
  authority_url: str = None,
31
- token_url: str = None,
32
44
  redirect_port: int = 3000,
33
- client_secret: str = None
45
+ user: str = None,
46
+ password: str = None,
47
+ proxy: str = None
34
48
  ):
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)
49
+ self._client_id = client_id
50
+ self._mode = mode
51
+ self._client_secret = client_secret
52
+ self._scopes = scopes
53
+ self._token_url = token_url
54
+ self._sdmx_resource_id = sdmx_resource_id
55
+ self._authority_url = authority_url
56
+ self._redirect_port = redirect_port
57
+ self._user = user
58
+ self._password = password
59
+
60
+ if proxy:
61
+ self._proxies = {
62
+ "http": proxy,
63
+ "https": proxy
64
+ }
65
+ else:
66
+ self._proxies = None
67
+
68
+ #
69
+ self._initialize_token()
58
70
 
71
+ #
59
72
  def __enter__(self):
60
73
  return self
61
74
 
75
+ #
62
76
  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
77
+ self._access_token = None
78
+ self._refresh_token = None
79
+ self._creation_time = None
80
+ self._expiration_time = None
81
+ self.init_status = None
67
82
 
83
+ #
84
+ def _initialize_token(self):
85
+ pass
86
+
87
+ #
88
+ def get_token(self):
89
+ if (self._access_token is None) \
90
+ or (datetime.fromtimestamp(self._expiration_time) is not None \
91
+ and datetime.now() >= datetime.fromtimestamp(self._expiration_time)):
92
+ self._initialize_token()
93
+
94
+ return self._access_token
95
+
96
+
97
+ # sub class to manage ADFS authentication using OIDC flows
98
+ class AdfsAuthentication(Authentication):
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,120 @@ 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._SUCCESS
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._SUCCESS
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._SUCCESS
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'
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._SUCCESS
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:
225
263
  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
-
264
+ self.init_status = self._ERROR_OCCURRED + os.linesep + str(response.text)
265
+ else:
266
+ self.init_status = self._ERROR_OCCURRED + os.linesep + str(err)
237
267
  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
268
+ self.init_status = f'{self._ERROR_OCCURRED} {err}\n'
269
+
270
+
271
+ # sub class to manage Keycloak authentication
272
+ class KeycloakAuthentication(Authentication):
273
+ #
274
+ def _initialize_token(self):
275
+ self._access_token = None
276
+ self._refresh_token = None
277
+ self._creation_time = None
278
+ self._expiration_time = None
279
+ self.init_status = None
280
+ match self._mode:
281
+ case AuthenticationMode.NONINTERACTIVE_WITH_SECRET:
282
+ self.__acquire_token_noninteractive_with_secret()
284
283
 
284
+ #
285
285
  @classmethod
286
- def nointeractive_with_secret(
286
+ def noninteractive_with_secret(
287
287
  cls,
288
288
  token_url: str,
289
289
  user: str,
@@ -305,31 +305,36 @@ class KeycloakAuthentication():
305
305
  mode=mode
306
306
  )
307
307
 
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()
308
+ # Authentication non-interactively using any account - aka Client Credentials flow
309
+ def __acquire_token_noninteractive_with_secret(self):
310
+ try:
311
+ payload = {
312
+ 'grant_type': 'password',
313
+ 'client_id': self._client_id,
314
+ 'client_secret': self._client_secret,
315
+ 'username': self._user,
316
+ 'password': self._password
317
+ }
314
318
 
315
- return self.__token
319
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
316
320
 
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
-
321
+ response = requests.post(self._token_url, proxies=self._proxies, headers=headers, data=payload)
322
+ if response.status_code not in {200, 201}:
323
+ self.init_status = f'{self._ERROR_OCCURRED}{response}'
324
+ else:
325
+ # If the response object cannot be converted to json, return an error
326
+ results_json = None
327
+ try:
328
+ results_json = json.loads(response.text)
329
+ self._access_token = results_json['access_token']
330
+ self._refresh_token = results_json['refresh_token']
331
+ self._creation_time = time.time()
332
+ self._expiration_time = time.time() + int(results_json['expires_in']) - 60 # one minute margin
333
+ self.init_status = self._SUCCESS
334
+ except ValueError as err:
335
+ if len(response.text) > 0:
336
+ self.init_status = self._ERROR_OCCURRED + os.linesep + str(response.text)
337
+ else:
338
+ self.init_status = self._ERROR_OCCURRED + os.linesep + str(err)
339
+ except Exception as err:
340
+ self.init_status = f'{self._ERROR_OCCURRED}{err}\n'