dotstat_io 0.2.7__tar.gz → 1.0.0__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dotstat_io
3
- Version: 0.2.7
3
+ Version: 1.0.0
4
4
  Summary: Utility to download or upload data from/to .Stat Suite using ADFS authentication to connect to it
5
5
  License: MIT
6
6
  Author: Gyorgy Gyomai
@@ -11,13 +11,15 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
14
15
  Requires-Dist: chardet (>=5.1.0,<6.0.0)
15
16
  Requires-Dist: msal (>=1.22.0,<2.0.0)
16
17
  Requires-Dist: requests (>=2.29.0,<3.0.0)
17
18
  Requires-Dist: requests-kerberos (>=0.14.0,<0.15.0)
19
+ Requires-Dist: setuptools (>=75.6.0,<76.0.0)
18
20
  Description-Content-Type: text/markdown
19
21
 
20
- ### DotStat_IO:
22
+ ### DotStat_IO package:
21
23
  A generic python package which could be integrated with other end-user applications and Gitlab runner to perform basic io with .Stat Suite.
22
24
  Its role is to mask the complexities of authentication to connect to .Stat Suite.
23
25
  The user needs to provide a set of parameters and the package exposes a couple of methods which will download or upload data from/to .Stat Suite.
@@ -25,19 +27,19 @@ The user needs to provide a set of parameters and the package exposes a couple o
25
27
  ### This package contains three modules:
26
28
  - ADFSAuthentication module
27
29
  - KeycloakAuthentication module
28
- - Download_upload module
30
+ - Client module
29
31
 
30
32
  ### In ADFSAuthentication module, four methods are available:
31
33
  #### 1. To initialise the module for interactive use:
32
34
  ```python
33
35
  from dotstat_io.authentication import AdfsAuthentication
34
- with AdfsAuthentication.interactive(
36
+ interactive_obj = AdfsAuthentication.interactive(
35
37
  client_id=client_id,
36
38
  sdmx_resource_id=sdmx_resource_id,
37
39
  scopes=scopes,
38
40
  authority_url=authority_url,
39
- redirect_port=redirect_port) as Interactive_obj:
40
- access_token = Interactive_obj.get_token()
41
+ redirect_port=redirect_port)
42
+
41
43
  ```
42
44
  * `client_id:` The Application (client) ID that the ADFS assigned to your app
43
45
  * `sdmx_resource_id:` The ID of the application to be accessed such as .Stat Suite
@@ -48,13 +50,13 @@ with AdfsAuthentication.interactive(
48
50
  #### 2. To initialise the module for non-interactive use using a secret:
49
51
  ```python
50
52
  from dotstat_io.authentication import AdfsAuthentication
51
- with AdfsAuthentication.nointeractive_with_secret(
53
+ noninteractive_with_secret_obj = AdfsAuthentication.noninteractive_with_secret(
52
54
  client_id=client_id,
53
55
  sdmx_resource_id=sdmx_resource_id,
54
56
  scopes=scopes,
55
57
  authority_url=authority_url,
56
- client_secret=client_secret) as Nointeractive_with_secret_obj:
57
- access_token = Nointeractive_with_secret_obj.get_token()
58
+ client_secret=client_secret)
59
+
58
60
  ```
59
61
  * `client_id:` The Application (client) ID that the ADFS assigned to your app
60
62
  * `sdmx_resource_id:` The ID of the application to be accessed such as .Stat Suite
@@ -65,11 +67,11 @@ with AdfsAuthentication.nointeractive_with_secret(
65
67
  #### 3. To initialise the module for non-interactive use using windows client authentication:
66
68
  ```python
67
69
  from dotstat_io.authentication import AdfsAuthentication
68
- with AdfsAuthentication.nointeractive_with_adfs(
70
+ noninteractive_with_adfs_obj = AdfsAuthentication.noninteractive_with_adfs(
69
71
  client_id=client_id,
70
72
  sdmx_resource_id=sdmx_resource_id,
71
- token_url=token_url) as Nointeractive_with_adfs_obj:
72
- access_token = Nointeractive_with_adfs_obj.get_token()
73
+ token_url=token_url)
74
+
73
75
  ```
74
76
  * `client_id:` The Application (client) ID that the ADFS assigned to your app
75
77
  * `sdmx_resource_id:` The ID of the application to be accessed such as .Stat Suite
@@ -84,12 +86,12 @@ access_token = [Authentication Object Name].get_token()
84
86
  #### 1. To initialise the module for Keycloak authentication:
85
87
  ```python
86
88
  from dotstat_io.authentication import KeycloakAuthentication
87
- with KeycloakAuthentication.nointeractive_with_secret(
89
+ noninteractive_with_keycloak_obj = KeycloakAuthentication.noninteractive_with_secret(
88
90
  user=user,
89
91
  password=password,
90
92
  token_url=token_url,
91
- proxy=proxy) as Nointeractive_with_keycloak_obj:
92
- access_token = Nointeractive_with_keycloak_obj.get_token()
93
+ proxy=proxy)
94
+
93
95
  ```
94
96
  * `user:` User name for .Stat Suite authentication
95
97
  * `password:` User name's password
@@ -101,33 +103,47 @@ with KeycloakAuthentication.nointeractive_with_secret(
101
103
  access_token = [Authentication Object Name].get_token()
102
104
  ```
103
105
 
104
- ### In Download_upload module, four methods are available:
105
- #### 1. To download a file from .Stat Suite:
106
+ ### In Client module, six methods are available:
107
+ #### 1. To initialise the module using an Authentication object type AdfsAuthentication or KeycloakAuthentication:
108
+ ```python
109
+ from dotstat_io.client import Client
110
+ client_obj = Client.init_with_authentication_obj(Authentication_obj)
111
+ ```
112
+ * `Authentication_obj:` An initialized authentication object type AdfsAuthentication or KeycloakAuthentication
113
+
114
+ #### 2. To initialise the module using an access token:
115
+ ```python
116
+ from dotstat_io.client import Client
117
+ client_obj = Client.init_with_access_token(access_token)
118
+ ```
119
+ * `access_token:` An access token to make requests on .Stat Suite services (nsiws) using the authorisation service and underlying permission rules
120
+
121
+ #### 3. To download a file from .Stat Suite:
106
122
  ```python
107
- from dotstat_io.download_upload import Download_upload
108
- with Download_upload(adfsAuthentication_obj, access_token) as Download_upload_obj:
109
- Returned_Message = Download_upload_obj.download_file(
123
+ from dotstat_io.client import Client
124
+ client_obj = Client.init_with_authentication_obj(Authentication_obj)
125
+ returned_result = client_obj.download_data_file(
110
126
  dotstat_url, content_format, Path(file_path))
111
127
  ```
112
128
  * `dotstat_url:` URL of data to be downloaded from .Stat Suite
113
129
  * `content_format:` Format of the downloaded content
114
130
  * `file_path:` The full path where the file will downloaded
115
131
 
116
- #### 2. To download streamed content from .Stat Suite:
132
+ #### 4. To download streamed content from .Stat Suite:
117
133
  ```python
118
- from dotstat_io.download_upload import Download_upload
119
- with Download_upload(adfsAuthentication_obj, access_token) as Download_upload_obj:
120
- Returned_Message = Download_upload_obj.download_stream(
134
+ from dotstat_io.client import Client
135
+ client_obj = Client.init_with_authentication_obj(Authentication_obj)
136
+ returned_result = client_obj.download_data_stream(
121
137
  dotstat_url, content_format)
122
138
  ```
123
139
  * `dotstat_url:` URL of data to be downloaded from .Stat Suite
124
140
  * `content_format:` Format of the downloaded content
125
141
 
126
- #### 3. To upload a data file to .Stat Suite:
142
+ #### 5. To upload a data file to .Stat Suite:
127
143
  ```python
128
- from dotstat_io.download_upload import Download_upload
129
- with Download_upload(adfsAuthentication_obj, access_token) as Download_upload_obj:
130
- Returned_Message = Download_upload_obj.upload_file(
144
+ from dotstat_io.client import Client
145
+ client_obj = Client.init_with_authentication_obj(Authentication_obj)
146
+ returned_result = client_obj.upload_data_file(
131
147
  transfer_url, Path(file_path), space, validationType, use_filepath)
132
148
  ```
133
149
  * `transfer_url:` URL of the transfer service
@@ -136,15 +152,15 @@ with Download_upload(adfsAuthentication_obj, access_token) as Download_upload_ob
136
152
  * `validationType:` The type of validation to use during upload. Possible values: Basic Validation (0), Advanced Validation (1)
137
153
  * `use_filepath:` Use a file path of a shared folder accessible by the .Stat Suite data upload engine (for unlimited file sizes)
138
154
 
139
- #### 4. To upload a structure to .Stat Suite:
155
+ #### 6. To upload a structure to .Stat Suite:
140
156
  ```python
141
- from dotstat_io.download_upload import Download_upload
142
- with Download_upload(adfsAuthentication_obj, access_token) as Download_upload_obj:
143
- Returned_Message = Download_upload_obj.upload_structure(
157
+ from dotstat_io.client import Client
158
+ client_obj = Client.init_with_authentication_obj(Authentication_obj)
159
+ returned_result = client_obj.upload_structure(
144
160
  transfer_url, Path(file_path))
145
161
  ```
146
162
  * `transfer_url:` URL of the transfer service
147
163
  * `file_path:` The full path of the SDMX-ML file, which will be uploaded to .Stat Suite
148
164
 
149
- ### For more information about how to use this package, all test cases can be accessed from this [`link`](https://gitlab.algobank.oecd.org/sdd-legacy/dotstat_io/-/blob/main/tests/test_cases.py)
165
+ ### For more information about how to use this package, all test cases can be accessed from this [`link`](https://gitlab.algobank.oecd.org/SD_ENGINEERING/dotstat_io/dotstat_io-package/-/blob/main/tests/test_cases.py)
150
166
 
@@ -1,4 +1,4 @@
1
- ### DotStat_IO:
1
+ ### DotStat_IO package:
2
2
  A generic python package which could be integrated with other end-user applications and Gitlab runner to perform basic io with .Stat Suite.
3
3
  Its role is to mask the complexities of authentication to connect to .Stat Suite.
4
4
  The user needs to provide a set of parameters and the package exposes a couple of methods which will download or upload data from/to .Stat Suite.
@@ -6,19 +6,19 @@ The user needs to provide a set of parameters and the package exposes a couple o
6
6
  ### This package contains three modules:
7
7
  - ADFSAuthentication module
8
8
  - KeycloakAuthentication module
9
- - Download_upload module
9
+ - Client module
10
10
 
11
11
  ### In ADFSAuthentication module, four methods are available:
12
12
  #### 1. To initialise the module for interactive use:
13
13
  ```python
14
14
  from dotstat_io.authentication import AdfsAuthentication
15
- with AdfsAuthentication.interactive(
15
+ interactive_obj = AdfsAuthentication.interactive(
16
16
  client_id=client_id,
17
17
  sdmx_resource_id=sdmx_resource_id,
18
18
  scopes=scopes,
19
19
  authority_url=authority_url,
20
- redirect_port=redirect_port) as Interactive_obj:
21
- access_token = Interactive_obj.get_token()
20
+ redirect_port=redirect_port)
21
+
22
22
  ```
23
23
  * `client_id:` The Application (client) ID that the ADFS assigned to your app
24
24
  * `sdmx_resource_id:` The ID of the application to be accessed such as .Stat Suite
@@ -29,13 +29,13 @@ with AdfsAuthentication.interactive(
29
29
  #### 2. To initialise the module for non-interactive use using a secret:
30
30
  ```python
31
31
  from dotstat_io.authentication import AdfsAuthentication
32
- with AdfsAuthentication.nointeractive_with_secret(
32
+ noninteractive_with_secret_obj = AdfsAuthentication.noninteractive_with_secret(
33
33
  client_id=client_id,
34
34
  sdmx_resource_id=sdmx_resource_id,
35
35
  scopes=scopes,
36
36
  authority_url=authority_url,
37
- client_secret=client_secret) as Nointeractive_with_secret_obj:
38
- access_token = Nointeractive_with_secret_obj.get_token()
37
+ client_secret=client_secret)
38
+
39
39
  ```
40
40
  * `client_id:` The Application (client) ID that the ADFS assigned to your app
41
41
  * `sdmx_resource_id:` The ID of the application to be accessed such as .Stat Suite
@@ -46,11 +46,11 @@ with AdfsAuthentication.nointeractive_with_secret(
46
46
  #### 3. To initialise the module for non-interactive use using windows client authentication:
47
47
  ```python
48
48
  from dotstat_io.authentication import AdfsAuthentication
49
- with AdfsAuthentication.nointeractive_with_adfs(
49
+ noninteractive_with_adfs_obj = AdfsAuthentication.noninteractive_with_adfs(
50
50
  client_id=client_id,
51
51
  sdmx_resource_id=sdmx_resource_id,
52
- token_url=token_url) as Nointeractive_with_adfs_obj:
53
- access_token = Nointeractive_with_adfs_obj.get_token()
52
+ token_url=token_url)
53
+
54
54
  ```
55
55
  * `client_id:` The Application (client) ID that the ADFS assigned to your app
56
56
  * `sdmx_resource_id:` The ID of the application to be accessed such as .Stat Suite
@@ -65,12 +65,12 @@ access_token = [Authentication Object Name].get_token()
65
65
  #### 1. To initialise the module for Keycloak authentication:
66
66
  ```python
67
67
  from dotstat_io.authentication import KeycloakAuthentication
68
- with KeycloakAuthentication.nointeractive_with_secret(
68
+ noninteractive_with_keycloak_obj = KeycloakAuthentication.noninteractive_with_secret(
69
69
  user=user,
70
70
  password=password,
71
71
  token_url=token_url,
72
- proxy=proxy) as Nointeractive_with_keycloak_obj:
73
- access_token = Nointeractive_with_keycloak_obj.get_token()
72
+ proxy=proxy)
73
+
74
74
  ```
75
75
  * `user:` User name for .Stat Suite authentication
76
76
  * `password:` User name's password
@@ -82,33 +82,47 @@ with KeycloakAuthentication.nointeractive_with_secret(
82
82
  access_token = [Authentication Object Name].get_token()
83
83
  ```
84
84
 
85
- ### In Download_upload module, four methods are available:
86
- #### 1. To download a file from .Stat Suite:
85
+ ### In Client module, six methods are available:
86
+ #### 1. To initialise the module using an Authentication object type AdfsAuthentication or KeycloakAuthentication:
87
+ ```python
88
+ from dotstat_io.client import Client
89
+ client_obj = Client.init_with_authentication_obj(Authentication_obj)
90
+ ```
91
+ * `Authentication_obj:` An initialized authentication object type AdfsAuthentication or KeycloakAuthentication
92
+
93
+ #### 2. To initialise the module using an access token:
94
+ ```python
95
+ from dotstat_io.client import Client
96
+ client_obj = Client.init_with_access_token(access_token)
97
+ ```
98
+ * `access_token:` An access token to make requests on .Stat Suite services (nsiws) using the authorisation service and underlying permission rules
99
+
100
+ #### 3. To download a file from .Stat Suite:
87
101
  ```python
88
- from dotstat_io.download_upload import Download_upload
89
- with Download_upload(adfsAuthentication_obj, access_token) as Download_upload_obj:
90
- Returned_Message = Download_upload_obj.download_file(
102
+ from dotstat_io.client import Client
103
+ client_obj = Client.init_with_authentication_obj(Authentication_obj)
104
+ returned_result = client_obj.download_data_file(
91
105
  dotstat_url, content_format, Path(file_path))
92
106
  ```
93
107
  * `dotstat_url:` URL of data to be downloaded from .Stat Suite
94
108
  * `content_format:` Format of the downloaded content
95
109
  * `file_path:` The full path where the file will downloaded
96
110
 
97
- #### 2. To download streamed content from .Stat Suite:
111
+ #### 4. To download streamed content from .Stat Suite:
98
112
  ```python
99
- from dotstat_io.download_upload import Download_upload
100
- with Download_upload(adfsAuthentication_obj, access_token) as Download_upload_obj:
101
- Returned_Message = Download_upload_obj.download_stream(
113
+ from dotstat_io.client import Client
114
+ client_obj = Client.init_with_authentication_obj(Authentication_obj)
115
+ returned_result = client_obj.download_data_stream(
102
116
  dotstat_url, content_format)
103
117
  ```
104
118
  * `dotstat_url:` URL of data to be downloaded from .Stat Suite
105
119
  * `content_format:` Format of the downloaded content
106
120
 
107
- #### 3. To upload a data file to .Stat Suite:
121
+ #### 5. To upload a data file to .Stat Suite:
108
122
  ```python
109
- from dotstat_io.download_upload import Download_upload
110
- with Download_upload(adfsAuthentication_obj, access_token) as Download_upload_obj:
111
- Returned_Message = Download_upload_obj.upload_file(
123
+ from dotstat_io.client import Client
124
+ client_obj = Client.init_with_authentication_obj(Authentication_obj)
125
+ returned_result = client_obj.upload_data_file(
112
126
  transfer_url, Path(file_path), space, validationType, use_filepath)
113
127
  ```
114
128
  * `transfer_url:` URL of the transfer service
@@ -117,14 +131,14 @@ with Download_upload(adfsAuthentication_obj, access_token) as Download_upload_ob
117
131
  * `validationType:` The type of validation to use during upload. Possible values: Basic Validation (0), Advanced Validation (1)
118
132
  * `use_filepath:` Use a file path of a shared folder accessible by the .Stat Suite data upload engine (for unlimited file sizes)
119
133
 
120
- #### 4. To upload a structure to .Stat Suite:
134
+ #### 6. To upload a structure to .Stat Suite:
121
135
  ```python
122
- from dotstat_io.download_upload import Download_upload
123
- with Download_upload(adfsAuthentication_obj, access_token) as Download_upload_obj:
124
- Returned_Message = Download_upload_obj.upload_structure(
136
+ from dotstat_io.client import Client
137
+ client_obj = Client.init_with_authentication_obj(Authentication_obj)
138
+ returned_result = client_obj.upload_structure(
125
139
  transfer_url, Path(file_path))
126
140
  ```
127
141
  * `transfer_url:` URL of the transfer service
128
142
  * `file_path:` The full path of the SDMX-ML file, which will be uploaded to .Stat Suite
129
143
 
130
- ### For more information about how to use this package, all test cases can be accessed from this [`link`](https://gitlab.algobank.oecd.org/sdd-legacy/dotstat_io/-/blob/main/tests/test_cases.py)
144
+ ### For more information about how to use this package, all test cases can be accessed from this [`link`](https://gitlab.algobank.oecd.org/SD_ENGINEERING/dotstat_io/dotstat_io-package/-/blob/main/tests/test_cases.py)
@@ -0,0 +1,340 @@
1
+ import requests
2
+ import os
3
+ import msal
4
+ import requests_kerberos
5
+ import json
6
+ import time
7
+
8
+ from abc import ABC
9
+ from enum import IntEnum
10
+ from datetime import datetime
11
+
12
+ #
13
+ class AuthenticationMode(IntEnum):
14
+ INTERACTIVE = 1
15
+ NONINTERACTIVE_WITH_SECRET = 2
16
+ NONINTERACTIVE_WITH_ADFS = 3
17
+
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
30
+
31
+ # public variables
32
+ init_status = None
33
+
34
+ # Initialise authentication
35
+ def __init__(
36
+ self,
37
+ client_id: str,
38
+ mode: AuthenticationMode,
39
+ client_secret: str = None,
40
+ scopes: list[str] = [],
41
+ token_url: str = None,
42
+ sdmx_resource_id: str = None,
43
+ authority_url: str = None,
44
+ redirect_port: int = 3000,
45
+ user: str = None,
46
+ password: str = None,
47
+ proxy: str = None
48
+ ):
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()
70
+
71
+ #
72
+ def __enter__(self):
73
+ return self
74
+
75
+ #
76
+ def __exit__(self, exc_type, exc_value, traceback):
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
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
+ #
119
+ @classmethod
120
+ def interactive(
121
+ cls,
122
+ client_id: str,
123
+ sdmx_resource_id: str,
124
+ scopes: list[str],
125
+ authority_url: str,
126
+ redirect_port: int = 3000,
127
+ mode: AuthenticationMode = AuthenticationMode.INTERACTIVE
128
+ ):
129
+ return cls(
130
+ client_id=client_id,
131
+ sdmx_resource_id=sdmx_resource_id,
132
+ scopes=scopes,
133
+ authority_url=authority_url,
134
+ redirect_port=redirect_port,
135
+ mode=mode
136
+ )
137
+
138
+ #
139
+ @classmethod
140
+ def noninteractive_with_secret(
141
+ cls,
142
+ client_id: str,
143
+ sdmx_resource_id: str,
144
+ scopes: list[str],
145
+ authority_url: str,
146
+ client_secret: str,
147
+ mode: AuthenticationMode = AuthenticationMode.NONINTERACTIVE_WITH_SECRET
148
+ ):
149
+ return cls(
150
+ client_id=client_id,
151
+ sdmx_resource_id=sdmx_resource_id,
152
+ scopes=scopes,
153
+ authority_url=authority_url,
154
+ client_secret=client_secret,
155
+ mode=mode
156
+ )
157
+
158
+ #
159
+ @classmethod
160
+ def noninteractive_with_adfs(
161
+ cls,
162
+ client_id: str,
163
+ sdmx_resource_id: str,
164
+ token_url: str,
165
+ mode: AuthenticationMode = AuthenticationMode.NONINTERACTIVE_WITH_ADFS
166
+ ):
167
+ return cls(
168
+ client_id=client_id,
169
+ sdmx_resource_id=sdmx_resource_id,
170
+ token_url=token_url,
171
+ mode=mode)
172
+
173
+ # Authentication interactively - aka Authorization Code flow
174
+ def __acquire_token_interactive(self):
175
+ try:
176
+ # We now check the cache to see
177
+ # whether we already have some accounts that the end user already used to sign in before.
178
+ accounts = self._app.get_accounts()
179
+ if accounts:
180
+ account = accounts[0]
181
+ else:
182
+ account = None
183
+
184
+ # Firstly, looks up a access_token from cache, or using a refresh token
185
+ response_silent = self._app.acquire_token_silent(
186
+ self._scopes, account=account)
187
+ if not response_silent:
188
+ # Prompt the user to sign in interactively
189
+ response_interactive = self._app.acquire_token_interactive(
190
+ scopes=self._scopes, port=self._redirect_port)
191
+ if "access_token" in response_interactive:
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
197
+ else:
198
+ self.init_status = f'{self._ERROR_OCCURRED}{response_interactive.get("error")} Error description: {response_interactive.get("error_description")}'
199
+ else:
200
+ if "access_token" in response_silent:
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
206
+ else:
207
+ self.init_status = f'{self._ERROR_OCCURRED}{response_silent.get("error")} Error description: {response_silent.get("error_description")}'
208
+ except Exception as err:
209
+ self.init_status = f'{self._ERROR_OCCURRED}{err}\n'
210
+
211
+ # Authentication non-interactively using any account - aka Client Credentials flow
212
+ def __acquire_token_noninteractive_with_secret(self):
213
+ try:
214
+ response = self._app.acquire_token_for_client(scopes=self._scopes)
215
+ if "access_token" in response:
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
220
+ else:
221
+ self.init_status = f'{self._ERROR_OCCURRED}{response.get("error")} Error description: {response.get("error_description")}'
222
+
223
+ except Exception as err:
224
+ self.init_status = f'{self._ERROR_OCCURRED}{err}\n'
225
+
226
+ # Authentication non-interactively using service account - aka Windows Client Authentication
227
+ def __acquire_token_noninteractive_with_adfs(self):
228
+ try:
229
+ headers = {
230
+ "Content-Type": "application/x-www-form-urlencoded"
231
+ }
232
+
233
+ payload = {
234
+ 'client_id': self._client_id,
235
+ 'use_windows_client_authentication': 'true',
236
+ 'grant_type': 'client_credentials',
237
+ 'scope': 'openid',
238
+ 'resource': self._sdmx_resource_id
239
+ }
240
+
241
+ kerberos_auth = requests_kerberos.HTTPKerberosAuth(
242
+ mutual_authentication=requests_kerberos.OPTIONAL, force_preemptive=True)
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:
263
+ if len(response.text) > 0:
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)
267
+ except Exception as err:
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()
283
+
284
+ #
285
+ @classmethod
286
+ def noninteractive_with_secret(
287
+ cls,
288
+ token_url: str,
289
+ user: str,
290
+ password: str,
291
+ client_id: str = "app",
292
+ client_secret: str = "",
293
+ proxy: str | None = None,
294
+ scopes: list[str] = [],
295
+ mode: AuthenticationMode = AuthenticationMode.NONINTERACTIVE_WITH_SECRET
296
+ ):
297
+ return cls(
298
+ token_url=token_url,
299
+ user=user,
300
+ password=password,
301
+ client_id=client_id,
302
+ client_secret=client_secret,
303
+ scopes=scopes,
304
+ proxy=proxy,
305
+ mode=mode
306
+ )
307
+
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
+ }
318
+
319
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
320
+
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'