python4cpm 1.0.22__tar.gz → 1.0.24__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.
- {python4cpm-1.0.22/src/python4cpm.egg-info → python4cpm-1.0.24}/PKG-INFO +48 -67
- {python4cpm-1.0.22 → python4cpm-1.0.24}/README.md +46 -44
- {python4cpm-1.0.22 → python4cpm-1.0.24}/pyproject.toml +3 -2
- {python4cpm-1.0.22 → python4cpm-1.0.24}/src/python4cpm/args.py +12 -5
- python4cpm-1.0.24/src/python4cpm/logger.py +44 -0
- {python4cpm-1.0.22 → python4cpm-1.0.24}/src/python4cpm/nethelper.py +4 -2
- {python4cpm-1.0.22 → python4cpm-1.0.24}/src/python4cpm/python4cpm.py +22 -26
- {python4cpm-1.0.22 → python4cpm-1.0.24/src/python4cpm.egg-info}/PKG-INFO +48 -67
- python4cpm-1.0.22/src/python4cpm/logger.py +0 -40
- {python4cpm-1.0.22 → python4cpm-1.0.24}/LICENSE +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.24}/setup.cfg +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.24}/src/python4cpm/__init__.py +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.24}/src/python4cpm/crypto.py +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.24}/src/python4cpm/python4cpmhandler.py +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.24}/src/python4cpm/secrets.py +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.24}/src/python4cpm.egg-info/SOURCES.txt +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.24}/src/python4cpm.egg-info/dependency_links.txt +0 -0
- {python4cpm-1.0.22 → python4cpm-1.0.24}/src/python4cpm.egg-info/top_level.txt +0 -0
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python4cpm
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.24
|
|
4
4
|
Summary: Python for CPM
|
|
5
5
|
Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2026 Gonzalo Atienza Rela
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in
|
|
18
|
-
all copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
26
|
-
THE SOFTWARE.
|
|
27
|
-
|
|
6
|
+
License-Expression: MIT
|
|
28
7
|
Requires-Python: >=3.10
|
|
29
8
|
Description-Content-Type: text/markdown
|
|
30
9
|
License-File: LICENSE
|
|
@@ -32,7 +11,7 @@ Dynamic: license-file
|
|
|
32
11
|
|
|
33
12
|
# Python4CPM
|
|
34
13
|
|
|
35
|
-
A simple way of using python scripts with CyberArk CPM rotations. This module leverages the [Credential Management .NET SDK](https://docs.cyberark.com/privilege-cloud-standard/latest/en/content/pasimp/plug-in-netinvoker.htm) from CyberArk to securely offload a password rotation logic into a python script.
|
|
14
|
+
A simple way of using python scripts with CyberArk CPM/SRS rotations. This module leverages the [Credential Management .NET SDK](https://docs.cyberark.com/privilege-cloud-standard/latest/en/content/pasimp/plug-in-netinvoker.htm) from CyberArk to securely offload a password rotation logic into a python script.
|
|
36
15
|
|
|
37
16
|
This platform allows you to duplicate it multiple times, simply changing its settings from Privilege Cloud/PVWA to point to different python scripts leveraging the module `python4cpm`.
|
|
38
17
|
|
|
@@ -40,14 +19,14 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
40
19
|
|
|
41
20
|
### Preparing Python
|
|
42
21
|
|
|
43
|
-
1. Install Python
|
|
22
|
+
1. Install Python along CPM or the SRS Connector Management Agent.
|
|
44
23
|
- **Python must be installed for all users**. Follow the custom install steps from the installation wizard to check the checkbox.
|
|
45
|
-
3. Create a venv in
|
|
24
|
+
3. Create a venv in the server, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
|
|
46
25
|
4. Install `python4cpm` in your venv:
|
|
47
26
|
- If your CPM can connect to the internet, install with `c:\venv\Scripts\pip install python4cpm`.
|
|
48
27
|
- If your CPM cannot connect to the internet:
|
|
49
28
|
- Download the latest `python4cpm-*.whl` file from the [pypi project files](https://pypi.org/project/python4cpm/#files).
|
|
50
|
-
- Copy the file to
|
|
29
|
+
- Copy the file to the server into a temporary directory called `python4cpm-wheel`.
|
|
51
30
|
- From the parent directory of `python4cpm-wheel` run `c:\venv\Scripts\pip install --no-index --find-links=.\python4cpm-wheel python4cpm`.
|
|
52
31
|
|
|
53
32
|
|
|
@@ -84,13 +63,14 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
84
63
|
from python4cpm import Python4CPMHandler
|
|
85
64
|
|
|
86
65
|
|
|
87
|
-
class
|
|
66
|
+
class MyRotator(Python4CPMHandler): # create a subclass for the Handler
|
|
88
67
|
"""
|
|
89
68
|
These are the usable properties and methods from Python4CPMHandler:
|
|
90
69
|
|
|
91
70
|
self.args.action # action requested from CPM/SRS
|
|
92
|
-
self.args.address # address from the account address field
|
|
93
71
|
self.args.username # username from the account username field
|
|
72
|
+
self.args.address # address from the account address field
|
|
73
|
+
self.args.port # port from the account port field
|
|
94
74
|
self.args.reconcile_username # reconcile username from the linked reconcile account
|
|
95
75
|
self.args.logon_username # logon username from the linked logon account
|
|
96
76
|
self.args.logging # used to carry the platform logging settings for python
|
|
@@ -101,13 +81,13 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
101
81
|
|
|
102
82
|
Logging methods -> Will only log if PythonLogging (platform parameters) is set to yes (default is yes)
|
|
103
83
|
|
|
104
|
-
self.log_error("this is an error message") # logs error into Logs/ThirdParty/
|
|
105
|
-
self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/
|
|
106
|
-
self.log_info("this is an info message") # logs info into Logs/ThirdParty/
|
|
84
|
+
self.log_error("this is an error message") # logs error into Logs/ThirdParty/MyRotator.log
|
|
85
|
+
self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/MyRotator.log
|
|
86
|
+
self.log_info("this is an info message") # logs info into Logs/ThirdParty/MyRotator.log
|
|
107
87
|
|
|
108
88
|
Logging level -> Will only log debug messages if PythonLoggingLevel (platform parameters) is set to debug (default is info)
|
|
109
89
|
|
|
110
|
-
self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/
|
|
90
|
+
self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/MyRotator.log if logging level is set to debug
|
|
111
91
|
|
|
112
92
|
=============================
|
|
113
93
|
REQUIRED TERMINATION SIGNALS
|
|
@@ -152,7 +132,7 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
152
132
|
def _verify(self, from_reconcile=False):
|
|
153
133
|
if from_reconcile is False:
|
|
154
134
|
pass
|
|
155
|
-
# TODO: use self.args.address, self.args.
|
|
135
|
+
# TODO: use self.args.username, self.args.address, self.args.port, self.secrets.password.get()
|
|
156
136
|
# for your logic in a verification
|
|
157
137
|
else:
|
|
158
138
|
pass
|
|
@@ -160,42 +140,42 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
160
140
|
# for your logic in a verification
|
|
161
141
|
result = True
|
|
162
142
|
if result is True:
|
|
163
|
-
self.log_info("verification successful") # logs info message into Logs/ThirdParty/
|
|
143
|
+
self.log_info("verification successful") # logs info message into Logs/ThirdParty/MyRotator.log
|
|
164
144
|
else:
|
|
165
|
-
self.log_error("something went wrong") # logs error message Logs/ThirdParty/
|
|
145
|
+
self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyRotator.log
|
|
166
146
|
self.close_fail()
|
|
167
147
|
|
|
168
148
|
def _change(self, from_reconcile=False):
|
|
169
149
|
if from_reconcile is False:
|
|
170
150
|
pass
|
|
171
|
-
# TODO: use self.args.address, self.args.
|
|
151
|
+
# TODO: use self.args.username, self.args.address, self.args.port, self.secrets.password.get()
|
|
172
152
|
# and self.secrets.new_password.get() for your logic in a rotation
|
|
173
153
|
else:
|
|
174
154
|
pass
|
|
175
|
-
# TODO: use self.args.address, self.args.
|
|
155
|
+
# TODO: use self.args.username, self.args.address, self.args.port, self.args.reconcile_username,
|
|
176
156
|
# self.secrets.reconcile_password.get() and self.secrets.new_password.get() for your logic in a reconciliation
|
|
177
157
|
result = True
|
|
178
158
|
if result is True:
|
|
179
|
-
self.log_info("rotation successful") # logs info message into Logs/ThirdParty/
|
|
159
|
+
self.log_info("rotation successful") # logs info message into Logs/ThirdParty/MyRotator.log
|
|
180
160
|
else:
|
|
181
|
-
self.log_error("something went wrong") # logs error message Logs/ThirdParty/
|
|
161
|
+
self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyRotator.log
|
|
182
162
|
self.close_fail()
|
|
183
163
|
|
|
184
164
|
|
|
185
165
|
if __name__ == "__main__":
|
|
186
|
-
|
|
166
|
+
MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
187
167
|
```
|
|
188
168
|
(*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples/python4cpmhandler).
|
|
189
169
|
|
|
190
170
|
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
191
|
-
1. Verify -> the sciprt will be executed once
|
|
192
|
-
2. Change -> the sciprt will be executed twice,
|
|
193
|
-
- If
|
|
194
|
-
3. Reconcile -> the sciprt will be executed twice,
|
|
195
|
-
- If
|
|
196
|
-
4. When calling `
|
|
197
|
-
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return
|
|
198
|
-
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return
|
|
171
|
+
1. Verify -> the sciprt will be executed once running the `MyRotator.verify()` method.
|
|
172
|
+
2. Change -> the sciprt will be executed twice, running first the `MyRotator.logon()` method and secondly the `MyRotator.change()` method.
|
|
173
|
+
- If both actions are not terminated with `self.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `self.close_fail(unrecoverable=True)`.
|
|
174
|
+
3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
|
|
175
|
+
- If both actions are not terminated with `self.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `self.close_fail(unrecoverable=True)`.
|
|
176
|
+
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.secrets.new_password.get()` will always return an empty string.
|
|
177
|
+
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return empty strings.
|
|
178
|
+
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return empty strings.
|
|
199
179
|
|
|
200
180
|
|
|
201
181
|
### Using Python4CPM properties and methods directly (for low level controls):
|
|
@@ -208,8 +188,9 @@ p4cpm = Python4CPM("MyApp") # this instantiates the object and grabs all argumen
|
|
|
208
188
|
|
|
209
189
|
# These are the usable properties and related methods from the object:
|
|
210
190
|
p4cpm.args.action # action requested from CPM/SRS
|
|
211
|
-
p4cpm.args.address # address from the account address field
|
|
212
191
|
p4cpm.args.username # username from the account username field
|
|
192
|
+
p4cpm.args.address # address from the account address field
|
|
193
|
+
p4cpm.args.port # port from the account port field
|
|
213
194
|
p4cpm.args.reconcile_username # reconcile username from the linked reconcile account
|
|
214
195
|
p4cpm.args.logon_username # logon username from the linked logon account
|
|
215
196
|
p4cpm.args.logging # used to carry the platform logging settings for python
|
|
@@ -236,11 +217,11 @@ p4cpm.log_debug("this is an debug message") # logs info into Logs/ThirdParty/MyA
|
|
|
236
217
|
def verify(from_reconcile=False):
|
|
237
218
|
if from_reconcile is False:
|
|
238
219
|
pass
|
|
239
|
-
# TODO: use p4cpm.args.address, p4cpm.args.
|
|
220
|
+
# TODO: use p4cpm.args.username, p4cpm.args.address, p4cpm.args.port, p4cpm.secrets.password.get()
|
|
240
221
|
# for your logic in a verification
|
|
241
222
|
else:
|
|
242
223
|
pass
|
|
243
|
-
# TODO: use p4cpm.args.address, p4cpm.args.reconcile_username, p4cpm.secrets.reconcile_password.get()
|
|
224
|
+
# TODO: use p4cpm.args.address, p4cpm.args.port, p4cpm.args.reconcile_username, p4cpm.secrets.reconcile_password.get()
|
|
244
225
|
# for your logic in a verification
|
|
245
226
|
result = True
|
|
246
227
|
if result is True:
|
|
@@ -254,11 +235,11 @@ def verify(from_reconcile=False):
|
|
|
254
235
|
def change(from_reconcile=False):
|
|
255
236
|
if from_reconcile is False:
|
|
256
237
|
pass
|
|
257
|
-
# TODO: use p4cpm.args.address, p4cpm.args.
|
|
238
|
+
# TODO: use p4cpm.args.username, p4cpm.args.address, p4cpm.args.port, p4cpm.secrets.password.get()
|
|
258
239
|
# and p4cpm.secrets.new_password.get() for your logic in a rotation
|
|
259
240
|
else:
|
|
260
241
|
pass
|
|
261
|
-
# TODO: use p4cpm.args.address, p4cpm.args.
|
|
242
|
+
# TODO: use p4cpm.args.username, p4cpm.args.address, p4cpm.args.port, p4cpm.args.reconcile_username,
|
|
262
243
|
# p4cpm.secrets.reconcile_password.get() and p4cpm.secrets.new_password.get() for your logic in a reconciliation
|
|
263
244
|
result = True
|
|
264
245
|
if result is True:
|
|
@@ -299,12 +280,12 @@ if __name__ == "__main__":
|
|
|
299
280
|
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
300
281
|
1. Verify -> the sciprt will be executed once with the `p4cpm.args.action` as `Python4CPM.ACTION_VERIFY`.
|
|
301
282
|
2. Change -> the sciprt will be executed twice, once with the action `p4cpm.args.action` as `Python4CPM.ACTION_LOGON` and once as `Python4CPM.ACTION_CHANGE`.
|
|
302
|
-
- If
|
|
283
|
+
- If both actions are not terminated with `p4cpm.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `p4cpm.close_fail(unrecoverable=True)`.
|
|
303
284
|
3. Reconcile -> the sciprt will be executed twice, once with the `p4cpm.args.action` as `Python4CPM.ACTION_PRERECONCILE` and once as `Python4CPM.ACTION_RECONCILE`.
|
|
304
|
-
- If
|
|
285
|
+
- If both actions are not terminated with `p4cpm.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `p4cpm.close_fail(unrecoverable=True)`.
|
|
305
286
|
4. When `p4cpm.args.action` comes as `Python4CPM.ACTION_VERIFY`, `Python4CPM.ACTION_LOGON` or `Python4CPM.ACTION_PRERECONCILE`: `p4cpm.secrets.new_password.get()` will always return an empty string.
|
|
306
|
-
5. If a logon account is not linked, `p4cpm.args.logon_username` and `p4cpm.secrets.logon_password.get()` will return
|
|
307
|
-
6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return
|
|
287
|
+
5. If a logon account is not linked, `p4cpm.args.logon_username` and `p4cpm.secrets.logon_password.get()` will return empty strings.
|
|
288
|
+
6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return empty strings.
|
|
308
289
|
|
|
309
290
|
|
|
310
291
|
### Installing dependencies in python venv
|
|
@@ -329,6 +310,8 @@ pip install python4cpm
|
|
|
329
310
|
|
|
330
311
|
### Example:
|
|
331
312
|
|
|
313
|
+
#### Set your arguments and secrets:
|
|
314
|
+
|
|
332
315
|
```python
|
|
333
316
|
from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
|
|
334
317
|
from getpass import getpass
|
|
@@ -339,15 +322,12 @@ password = getpass("password: ") # password from account
|
|
|
339
322
|
logon_password = getpass("logon_password: ") # password from linked logon account
|
|
340
323
|
reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
|
|
341
324
|
new_password = getpass("new_password: ") # new password for the rotation
|
|
342
|
-
```
|
|
343
325
|
|
|
344
|
-
#### Set your arguments and secrets:
|
|
345
|
-
|
|
346
|
-
```python
|
|
347
326
|
NETHelper.set(
|
|
348
327
|
action=Python4CPM.ACTION_LOGON, # use actions from Python4CPM.ACTION_*
|
|
349
|
-
address="myapp.corp.local", # populate with the address from your account properties
|
|
350
328
|
username="jdoe", # populate with the username from your account properties
|
|
329
|
+
address="myapp.corp.local", # populate with the address from your account properties
|
|
330
|
+
port="8443", # populate with the port from your account properties
|
|
351
331
|
logon_username="ldoe", # populate with the logon account username from your linked logon account
|
|
352
332
|
reconcile_username="rdoe", # ppopulate with the reconcile account username from your linked logon account
|
|
353
333
|
logging="yes", # populate with the PythonLogging parameter from the platform: "yes" or "no"
|
|
@@ -362,7 +342,7 @@ NETHelper.set(
|
|
|
362
342
|
#### Using the handler (recommended):
|
|
363
343
|
|
|
364
344
|
```python
|
|
365
|
-
class
|
|
345
|
+
class MyRotator(Python4CPMHandler):
|
|
366
346
|
def verify(self):
|
|
367
347
|
# TODO: Add your logic here
|
|
368
348
|
self.close_success()
|
|
@@ -383,7 +363,7 @@ class MyApp(Python4CPMHandler):
|
|
|
383
363
|
# TODO: Add your logic here
|
|
384
364
|
self.close_success()
|
|
385
365
|
|
|
386
|
-
|
|
366
|
+
MyRotator().run()
|
|
387
367
|
```
|
|
388
368
|
|
|
389
369
|
#### Using Python4CPM properties and methods directly:
|
|
@@ -399,6 +379,7 @@ p4cpm.close_success()
|
|
|
399
379
|
|
|
400
380
|
#### Remember for your final script:
|
|
401
381
|
|
|
402
|
-
- Change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")` if you are using the properties and methods directly.
|
|
403
|
-
- Remove any secrets prompting or interactive interruptions.
|
|
404
382
|
- Remove the import of `NETHelper`.
|
|
383
|
+
- Remove the `NETHelper.set()` call.
|
|
384
|
+
- If applicable, change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")`.
|
|
385
|
+
- Remove any secrets prompting or interactive interruptions.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Python4CPM
|
|
2
2
|
|
|
3
|
-
A simple way of using python scripts with CyberArk CPM rotations. This module leverages the [Credential Management .NET SDK](https://docs.cyberark.com/privilege-cloud-standard/latest/en/content/pasimp/plug-in-netinvoker.htm) from CyberArk to securely offload a password rotation logic into a python script.
|
|
3
|
+
A simple way of using python scripts with CyberArk CPM/SRS rotations. This module leverages the [Credential Management .NET SDK](https://docs.cyberark.com/privilege-cloud-standard/latest/en/content/pasimp/plug-in-netinvoker.htm) from CyberArk to securely offload a password rotation logic into a python script.
|
|
4
4
|
|
|
5
5
|
This platform allows you to duplicate it multiple times, simply changing its settings from Privilege Cloud/PVWA to point to different python scripts leveraging the module `python4cpm`.
|
|
6
6
|
|
|
@@ -8,14 +8,14 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
8
8
|
|
|
9
9
|
### Preparing Python
|
|
10
10
|
|
|
11
|
-
1. Install Python
|
|
11
|
+
1. Install Python along CPM or the SRS Connector Management Agent.
|
|
12
12
|
- **Python must be installed for all users**. Follow the custom install steps from the installation wizard to check the checkbox.
|
|
13
|
-
3. Create a venv in
|
|
13
|
+
3. Create a venv in the server, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
|
|
14
14
|
4. Install `python4cpm` in your venv:
|
|
15
15
|
- If your CPM can connect to the internet, install with `c:\venv\Scripts\pip install python4cpm`.
|
|
16
16
|
- If your CPM cannot connect to the internet:
|
|
17
17
|
- Download the latest `python4cpm-*.whl` file from the [pypi project files](https://pypi.org/project/python4cpm/#files).
|
|
18
|
-
- Copy the file to
|
|
18
|
+
- Copy the file to the server into a temporary directory called `python4cpm-wheel`.
|
|
19
19
|
- From the parent directory of `python4cpm-wheel` run `c:\venv\Scripts\pip install --no-index --find-links=.\python4cpm-wheel python4cpm`.
|
|
20
20
|
|
|
21
21
|
|
|
@@ -52,13 +52,14 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
52
52
|
from python4cpm import Python4CPMHandler
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
class
|
|
55
|
+
class MyRotator(Python4CPMHandler): # create a subclass for the Handler
|
|
56
56
|
"""
|
|
57
57
|
These are the usable properties and methods from Python4CPMHandler:
|
|
58
58
|
|
|
59
59
|
self.args.action # action requested from CPM/SRS
|
|
60
|
-
self.args.address # address from the account address field
|
|
61
60
|
self.args.username # username from the account username field
|
|
61
|
+
self.args.address # address from the account address field
|
|
62
|
+
self.args.port # port from the account port field
|
|
62
63
|
self.args.reconcile_username # reconcile username from the linked reconcile account
|
|
63
64
|
self.args.logon_username # logon username from the linked logon account
|
|
64
65
|
self.args.logging # used to carry the platform logging settings for python
|
|
@@ -69,13 +70,13 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
69
70
|
|
|
70
71
|
Logging methods -> Will only log if PythonLogging (platform parameters) is set to yes (default is yes)
|
|
71
72
|
|
|
72
|
-
self.log_error("this is an error message") # logs error into Logs/ThirdParty/
|
|
73
|
-
self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/
|
|
74
|
-
self.log_info("this is an info message") # logs info into Logs/ThirdParty/
|
|
73
|
+
self.log_error("this is an error message") # logs error into Logs/ThirdParty/MyRotator.log
|
|
74
|
+
self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/MyRotator.log
|
|
75
|
+
self.log_info("this is an info message") # logs info into Logs/ThirdParty/MyRotator.log
|
|
75
76
|
|
|
76
77
|
Logging level -> Will only log debug messages if PythonLoggingLevel (platform parameters) is set to debug (default is info)
|
|
77
78
|
|
|
78
|
-
self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/
|
|
79
|
+
self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/MyRotator.log if logging level is set to debug
|
|
79
80
|
|
|
80
81
|
=============================
|
|
81
82
|
REQUIRED TERMINATION SIGNALS
|
|
@@ -120,7 +121,7 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
120
121
|
def _verify(self, from_reconcile=False):
|
|
121
122
|
if from_reconcile is False:
|
|
122
123
|
pass
|
|
123
|
-
# TODO: use self.args.address, self.args.
|
|
124
|
+
# TODO: use self.args.username, self.args.address, self.args.port, self.secrets.password.get()
|
|
124
125
|
# for your logic in a verification
|
|
125
126
|
else:
|
|
126
127
|
pass
|
|
@@ -128,42 +129,42 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
128
129
|
# for your logic in a verification
|
|
129
130
|
result = True
|
|
130
131
|
if result is True:
|
|
131
|
-
self.log_info("verification successful") # logs info message into Logs/ThirdParty/
|
|
132
|
+
self.log_info("verification successful") # logs info message into Logs/ThirdParty/MyRotator.log
|
|
132
133
|
else:
|
|
133
|
-
self.log_error("something went wrong") # logs error message Logs/ThirdParty/
|
|
134
|
+
self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyRotator.log
|
|
134
135
|
self.close_fail()
|
|
135
136
|
|
|
136
137
|
def _change(self, from_reconcile=False):
|
|
137
138
|
if from_reconcile is False:
|
|
138
139
|
pass
|
|
139
|
-
# TODO: use self.args.address, self.args.
|
|
140
|
+
# TODO: use self.args.username, self.args.address, self.args.port, self.secrets.password.get()
|
|
140
141
|
# and self.secrets.new_password.get() for your logic in a rotation
|
|
141
142
|
else:
|
|
142
143
|
pass
|
|
143
|
-
# TODO: use self.args.address, self.args.
|
|
144
|
+
# TODO: use self.args.username, self.args.address, self.args.port, self.args.reconcile_username,
|
|
144
145
|
# self.secrets.reconcile_password.get() and self.secrets.new_password.get() for your logic in a reconciliation
|
|
145
146
|
result = True
|
|
146
147
|
if result is True:
|
|
147
|
-
self.log_info("rotation successful") # logs info message into Logs/ThirdParty/
|
|
148
|
+
self.log_info("rotation successful") # logs info message into Logs/ThirdParty/MyRotator.log
|
|
148
149
|
else:
|
|
149
|
-
self.log_error("something went wrong") # logs error message Logs/ThirdParty/
|
|
150
|
+
self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyRotator.log
|
|
150
151
|
self.close_fail()
|
|
151
152
|
|
|
152
153
|
|
|
153
154
|
if __name__ == "__main__":
|
|
154
|
-
|
|
155
|
+
MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
155
156
|
```
|
|
156
157
|
(*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples/python4cpmhandler).
|
|
157
158
|
|
|
158
159
|
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
159
|
-
1. Verify -> the sciprt will be executed once
|
|
160
|
-
2. Change -> the sciprt will be executed twice,
|
|
161
|
-
- If
|
|
162
|
-
3. Reconcile -> the sciprt will be executed twice,
|
|
163
|
-
- If
|
|
164
|
-
4. When calling `
|
|
165
|
-
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return
|
|
166
|
-
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return
|
|
160
|
+
1. Verify -> the sciprt will be executed once running the `MyRotator.verify()` method.
|
|
161
|
+
2. Change -> the sciprt will be executed twice, running first the `MyRotator.logon()` method and secondly the `MyRotator.change()` method.
|
|
162
|
+
- If both actions are not terminated with `self.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `self.close_fail(unrecoverable=True)`.
|
|
163
|
+
3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
|
|
164
|
+
- If both actions are not terminated with `self.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `self.close_fail(unrecoverable=True)`.
|
|
165
|
+
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.secrets.new_password.get()` will always return an empty string.
|
|
166
|
+
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return empty strings.
|
|
167
|
+
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return empty strings.
|
|
167
168
|
|
|
168
169
|
|
|
169
170
|
### Using Python4CPM properties and methods directly (for low level controls):
|
|
@@ -176,8 +177,9 @@ p4cpm = Python4CPM("MyApp") # this instantiates the object and grabs all argumen
|
|
|
176
177
|
|
|
177
178
|
# These are the usable properties and related methods from the object:
|
|
178
179
|
p4cpm.args.action # action requested from CPM/SRS
|
|
179
|
-
p4cpm.args.address # address from the account address field
|
|
180
180
|
p4cpm.args.username # username from the account username field
|
|
181
|
+
p4cpm.args.address # address from the account address field
|
|
182
|
+
p4cpm.args.port # port from the account port field
|
|
181
183
|
p4cpm.args.reconcile_username # reconcile username from the linked reconcile account
|
|
182
184
|
p4cpm.args.logon_username # logon username from the linked logon account
|
|
183
185
|
p4cpm.args.logging # used to carry the platform logging settings for python
|
|
@@ -204,11 +206,11 @@ p4cpm.log_debug("this is an debug message") # logs info into Logs/ThirdParty/MyA
|
|
|
204
206
|
def verify(from_reconcile=False):
|
|
205
207
|
if from_reconcile is False:
|
|
206
208
|
pass
|
|
207
|
-
# TODO: use p4cpm.args.address, p4cpm.args.
|
|
209
|
+
# TODO: use p4cpm.args.username, p4cpm.args.address, p4cpm.args.port, p4cpm.secrets.password.get()
|
|
208
210
|
# for your logic in a verification
|
|
209
211
|
else:
|
|
210
212
|
pass
|
|
211
|
-
# TODO: use p4cpm.args.address, p4cpm.args.reconcile_username, p4cpm.secrets.reconcile_password.get()
|
|
213
|
+
# TODO: use p4cpm.args.address, p4cpm.args.port, p4cpm.args.reconcile_username, p4cpm.secrets.reconcile_password.get()
|
|
212
214
|
# for your logic in a verification
|
|
213
215
|
result = True
|
|
214
216
|
if result is True:
|
|
@@ -222,11 +224,11 @@ def verify(from_reconcile=False):
|
|
|
222
224
|
def change(from_reconcile=False):
|
|
223
225
|
if from_reconcile is False:
|
|
224
226
|
pass
|
|
225
|
-
# TODO: use p4cpm.args.address, p4cpm.args.
|
|
227
|
+
# TODO: use p4cpm.args.username, p4cpm.args.address, p4cpm.args.port, p4cpm.secrets.password.get()
|
|
226
228
|
# and p4cpm.secrets.new_password.get() for your logic in a rotation
|
|
227
229
|
else:
|
|
228
230
|
pass
|
|
229
|
-
# TODO: use p4cpm.args.address, p4cpm.args.
|
|
231
|
+
# TODO: use p4cpm.args.username, p4cpm.args.address, p4cpm.args.port, p4cpm.args.reconcile_username,
|
|
230
232
|
# p4cpm.secrets.reconcile_password.get() and p4cpm.secrets.new_password.get() for your logic in a reconciliation
|
|
231
233
|
result = True
|
|
232
234
|
if result is True:
|
|
@@ -267,12 +269,12 @@ if __name__ == "__main__":
|
|
|
267
269
|
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
268
270
|
1. Verify -> the sciprt will be executed once with the `p4cpm.args.action` as `Python4CPM.ACTION_VERIFY`.
|
|
269
271
|
2. Change -> the sciprt will be executed twice, once with the action `p4cpm.args.action` as `Python4CPM.ACTION_LOGON` and once as `Python4CPM.ACTION_CHANGE`.
|
|
270
|
-
- If
|
|
272
|
+
- If both actions are not terminated with `p4cpm.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `p4cpm.close_fail(unrecoverable=True)`.
|
|
271
273
|
3. Reconcile -> the sciprt will be executed twice, once with the `p4cpm.args.action` as `Python4CPM.ACTION_PRERECONCILE` and once as `Python4CPM.ACTION_RECONCILE`.
|
|
272
|
-
- If
|
|
274
|
+
- If both actions are not terminated with `p4cpm.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `p4cpm.close_fail(unrecoverable=True)`.
|
|
273
275
|
4. When `p4cpm.args.action` comes as `Python4CPM.ACTION_VERIFY`, `Python4CPM.ACTION_LOGON` or `Python4CPM.ACTION_PRERECONCILE`: `p4cpm.secrets.new_password.get()` will always return an empty string.
|
|
274
|
-
5. If a logon account is not linked, `p4cpm.args.logon_username` and `p4cpm.secrets.logon_password.get()` will return
|
|
275
|
-
6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return
|
|
276
|
+
5. If a logon account is not linked, `p4cpm.args.logon_username` and `p4cpm.secrets.logon_password.get()` will return empty strings.
|
|
277
|
+
6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return empty strings.
|
|
276
278
|
|
|
277
279
|
|
|
278
280
|
### Installing dependencies in python venv
|
|
@@ -297,6 +299,8 @@ pip install python4cpm
|
|
|
297
299
|
|
|
298
300
|
### Example:
|
|
299
301
|
|
|
302
|
+
#### Set your arguments and secrets:
|
|
303
|
+
|
|
300
304
|
```python
|
|
301
305
|
from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
|
|
302
306
|
from getpass import getpass
|
|
@@ -307,15 +311,12 @@ password = getpass("password: ") # password from account
|
|
|
307
311
|
logon_password = getpass("logon_password: ") # password from linked logon account
|
|
308
312
|
reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
|
|
309
313
|
new_password = getpass("new_password: ") # new password for the rotation
|
|
310
|
-
```
|
|
311
314
|
|
|
312
|
-
#### Set your arguments and secrets:
|
|
313
|
-
|
|
314
|
-
```python
|
|
315
315
|
NETHelper.set(
|
|
316
316
|
action=Python4CPM.ACTION_LOGON, # use actions from Python4CPM.ACTION_*
|
|
317
|
-
address="myapp.corp.local", # populate with the address from your account properties
|
|
318
317
|
username="jdoe", # populate with the username from your account properties
|
|
318
|
+
address="myapp.corp.local", # populate with the address from your account properties
|
|
319
|
+
port="8443", # populate with the port from your account properties
|
|
319
320
|
logon_username="ldoe", # populate with the logon account username from your linked logon account
|
|
320
321
|
reconcile_username="rdoe", # ppopulate with the reconcile account username from your linked logon account
|
|
321
322
|
logging="yes", # populate with the PythonLogging parameter from the platform: "yes" or "no"
|
|
@@ -330,7 +331,7 @@ NETHelper.set(
|
|
|
330
331
|
#### Using the handler (recommended):
|
|
331
332
|
|
|
332
333
|
```python
|
|
333
|
-
class
|
|
334
|
+
class MyRotator(Python4CPMHandler):
|
|
334
335
|
def verify(self):
|
|
335
336
|
# TODO: Add your logic here
|
|
336
337
|
self.close_success()
|
|
@@ -351,7 +352,7 @@ class MyApp(Python4CPMHandler):
|
|
|
351
352
|
# TODO: Add your logic here
|
|
352
353
|
self.close_success()
|
|
353
354
|
|
|
354
|
-
|
|
355
|
+
MyRotator().run()
|
|
355
356
|
```
|
|
356
357
|
|
|
357
358
|
#### Using Python4CPM properties and methods directly:
|
|
@@ -367,6 +368,7 @@ p4cpm.close_success()
|
|
|
367
368
|
|
|
368
369
|
#### Remember for your final script:
|
|
369
370
|
|
|
370
|
-
- Change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")` if you are using the properties and methods directly.
|
|
371
|
-
- Remove any secrets prompting or interactive interruptions.
|
|
372
371
|
- Remove the import of `NETHelper`.
|
|
372
|
+
- Remove the `NETHelper.set()` call.
|
|
373
|
+
- If applicable, change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")`.
|
|
374
|
+
- Remove any secrets prompting or interactive interruptions.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python4cpm"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.24"
|
|
8
8
|
description = "Python for CPM"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Gonzalo Atienza Rela", email = "gonatienza@gmail.com" }
|
|
@@ -12,7 +12,8 @@ authors = [
|
|
|
12
12
|
dependencies = []
|
|
13
13
|
requires-python = ">=3.10"
|
|
14
14
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
15
|
-
license =
|
|
15
|
+
license = "MIT"
|
|
16
|
+
license-files = ["LICENSE"]
|
|
16
17
|
|
|
17
18
|
[tool.setuptools]
|
|
18
19
|
package-dir = { "" = "src" }
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
class Args:
|
|
2
2
|
ARGS = (
|
|
3
3
|
"action",
|
|
4
|
-
"address",
|
|
5
4
|
"username",
|
|
5
|
+
"address",
|
|
6
|
+
"port",
|
|
6
7
|
"logon_username",
|
|
7
8
|
"reconcile_username",
|
|
8
9
|
"logging",
|
|
@@ -12,16 +13,18 @@ class Args:
|
|
|
12
13
|
def __init__(
|
|
13
14
|
self: str,
|
|
14
15
|
action: str,
|
|
15
|
-
address: str,
|
|
16
16
|
username: str,
|
|
17
|
+
address: str,
|
|
18
|
+
port: str,
|
|
17
19
|
reconcile_username: str,
|
|
18
20
|
logon_username: str,
|
|
19
21
|
logging: str,
|
|
20
22
|
logging_level: str
|
|
21
23
|
) -> None:
|
|
22
24
|
self._action = action
|
|
23
|
-
self._address = address
|
|
24
25
|
self._username = username
|
|
26
|
+
self._address = address
|
|
27
|
+
self._port = port
|
|
25
28
|
self._reconcile_username = reconcile_username
|
|
26
29
|
self._logon_username = logon_username
|
|
27
30
|
self._logging = logging
|
|
@@ -31,13 +34,17 @@ class Args:
|
|
|
31
34
|
def action(self) -> str:
|
|
32
35
|
return self._action
|
|
33
36
|
|
|
37
|
+
@property
|
|
38
|
+
def username(self) -> str:
|
|
39
|
+
return self._username
|
|
40
|
+
|
|
34
41
|
@property
|
|
35
42
|
def address(self) -> str:
|
|
36
43
|
return self._address
|
|
37
44
|
|
|
38
45
|
@property
|
|
39
|
-
def
|
|
40
|
-
return self.
|
|
46
|
+
def port(self) -> str:
|
|
47
|
+
return self._port
|
|
41
48
|
|
|
42
49
|
@property
|
|
43
50
|
def reconcile_username(self) -> str:
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
from logging.handlers import RotatingFileHandler
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Logger:
|
|
7
|
+
_LOGS_DIR = os.path.join("Logs", "ThirdParty")
|
|
8
|
+
_LOGGING_ENABLED_VALUE = "yes"
|
|
9
|
+
_LOGGING_LEVELS = {
|
|
10
|
+
"info": logging.INFO,
|
|
11
|
+
"debug": logging.DEBUG
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def get_logger(
|
|
16
|
+
cls,
|
|
17
|
+
name: str,
|
|
18
|
+
args_logging: str,
|
|
19
|
+
args_logging_level: str
|
|
20
|
+
) -> logging.Logger:
|
|
21
|
+
if args_logging.lower() != cls._LOGGING_ENABLED_VALUE:
|
|
22
|
+
return None
|
|
23
|
+
os.makedirs(cls._LOGS_DIR, exist_ok=True)
|
|
24
|
+
logs_file = os.path.join(cls._LOGS_DIR, f"{__name__}-{name}.log")
|
|
25
|
+
_id = os.urandom(4).hex()
|
|
26
|
+
logger = logging.getLogger(_id)
|
|
27
|
+
if args_logging_level.lower() in cls._LOGGING_LEVELS:
|
|
28
|
+
logger.setLevel(cls._LOGGING_LEVELS[args_logging_level.lower()])
|
|
29
|
+
else:
|
|
30
|
+
logger.setLevel(cls._LOGGING_LEVELS["info"])
|
|
31
|
+
handler = RotatingFileHandler(
|
|
32
|
+
filename=logs_file,
|
|
33
|
+
maxBytes=1024 ** 2,
|
|
34
|
+
backupCount=1
|
|
35
|
+
)
|
|
36
|
+
fmt = (
|
|
37
|
+
"%(asctime)s.%(msecs)03d | %(levelname)s | %(name)s | "
|
|
38
|
+
"%(module)s | %(funcName)s | %(message)s"
|
|
39
|
+
)
|
|
40
|
+
datefmt = "%Y-%m-%d %H:%M:%S"
|
|
41
|
+
formatter = logging.Formatter(fmt=fmt, datefmt=datefmt)
|
|
42
|
+
handler.setFormatter(formatter)
|
|
43
|
+
logger.addHandler(handler)
|
|
44
|
+
return logger
|
|
@@ -10,8 +10,9 @@ class NETHelper:
|
|
|
10
10
|
def set(
|
|
11
11
|
cls,
|
|
12
12
|
action: str = "",
|
|
13
|
-
address: str = "",
|
|
14
13
|
username: str = "",
|
|
14
|
+
address: str = "",
|
|
15
|
+
port: str = "",
|
|
15
16
|
logon_username: str = "",
|
|
16
17
|
reconcile_username: str = "",
|
|
17
18
|
logging: str = "",
|
|
@@ -23,8 +24,9 @@ class NETHelper:
|
|
|
23
24
|
) -> None:
|
|
24
25
|
_args = [
|
|
25
26
|
action,
|
|
26
|
-
address,
|
|
27
27
|
username,
|
|
28
|
+
address,
|
|
29
|
+
port,
|
|
28
30
|
logon_username,
|
|
29
31
|
reconcile_username,
|
|
30
32
|
logging,
|
|
@@ -3,7 +3,7 @@ import sys
|
|
|
3
3
|
import atexit
|
|
4
4
|
from python4cpm.secrets import Secrets
|
|
5
5
|
from python4cpm.args import Args
|
|
6
|
-
from python4cpm.logger import
|
|
6
|
+
from python4cpm.logger import Logger
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class Python4CPM:
|
|
@@ -27,15 +27,16 @@ class Python4CPM:
|
|
|
27
27
|
def __init__(self, name: str) -> None:
|
|
28
28
|
self._name = name
|
|
29
29
|
self._args = self._get_args()
|
|
30
|
-
self._logger = get_logger(
|
|
30
|
+
self._logger = Logger.get_logger(
|
|
31
31
|
self._name,
|
|
32
32
|
self._args.logging,
|
|
33
33
|
self._args.logging_level
|
|
34
34
|
)
|
|
35
|
-
self.
|
|
36
|
-
self.
|
|
35
|
+
self.log_debug("Initiating...")
|
|
36
|
+
self._log_env(self._args, False)
|
|
37
37
|
self._verify_action()
|
|
38
38
|
self._secrets = self._get_secrets()
|
|
39
|
+
self._log_env(self._secrets)
|
|
39
40
|
self._closed = False
|
|
40
41
|
atexit.register(self._on_exit)
|
|
41
42
|
|
|
@@ -49,19 +50,19 @@ class Python4CPM:
|
|
|
49
50
|
|
|
50
51
|
def log_debug(self, message: str) -> None:
|
|
51
52
|
if self._logger is not None:
|
|
52
|
-
self._logger.debug(message)
|
|
53
|
+
self._logger.debug(message, stacklevel=2)
|
|
53
54
|
|
|
54
55
|
def log_info(self, message: str) -> None:
|
|
55
56
|
if self._logger is not None:
|
|
56
|
-
self._logger.info(message)
|
|
57
|
+
self._logger.info(message, stacklevel=2)
|
|
57
58
|
|
|
58
59
|
def log_warning(self, message: str) -> None:
|
|
59
60
|
if self._logger is not None:
|
|
60
|
-
self._logger.warning(message)
|
|
61
|
+
self._logger.warning(message, stacklevel=2)
|
|
61
62
|
|
|
62
63
|
def log_error(self, message: str) -> None:
|
|
63
64
|
if self._logger is not None:
|
|
64
|
-
self._logger.error(message)
|
|
65
|
+
self._logger.error(message, stacklevel=2)
|
|
65
66
|
|
|
66
67
|
@classmethod
|
|
67
68
|
def _get_env_key(cls, key: str) -> str:
|
|
@@ -80,46 +81,41 @@ class Python4CPM:
|
|
|
80
81
|
for secret in Secrets.SECRETS:
|
|
81
82
|
_secret = os.environ.get(self._get_env_key(secret))
|
|
82
83
|
secrets[secret] = _secret if _secret is not None else ""
|
|
83
|
-
common_message = f"Python4CPM._get_secrets: {secret} ->"
|
|
84
|
-
if secrets[secret]:
|
|
85
|
-
self.log_info(f"{common_message} [SET]")
|
|
86
|
-
else:
|
|
87
|
-
self.log_info(f"{common_message} [NOT SET]")
|
|
88
84
|
return Secrets(**secrets)
|
|
89
85
|
|
|
90
86
|
def _verify_action(self) -> None:
|
|
91
87
|
if self._args.action not in self._VALID_ACTIONS:
|
|
92
|
-
self.log_warning(
|
|
93
|
-
f"Python4CPM._verify_action: unkonwn action -> {self._args.action}"
|
|
94
|
-
)
|
|
88
|
+
self.log_warning(f"Unkonwn action -> '{self._args.action}'")
|
|
95
89
|
|
|
96
|
-
def
|
|
97
|
-
for key, value in vars(
|
|
98
|
-
|
|
90
|
+
def _log_env(self, obj: object, masked: bool = True) -> None:
|
|
91
|
+
for key, value in vars(obj).items():
|
|
92
|
+
_key = key.strip('_')
|
|
99
93
|
if value:
|
|
100
|
-
|
|
94
|
+
if masked:
|
|
95
|
+
logging_value = "[SET]"
|
|
96
|
+
else:
|
|
97
|
+
logging_value = value
|
|
101
98
|
else:
|
|
102
|
-
|
|
99
|
+
logging_value = "[NOT SET]"
|
|
100
|
+
self.log_debug(f"{_key} -> '{logging_value}'")
|
|
103
101
|
|
|
104
102
|
def close_fail(self, unrecoverable: bool = False) -> None:
|
|
105
103
|
if unrecoverable is False:
|
|
106
104
|
code = self._FAILED_RECOVERABLE_CODE
|
|
107
105
|
else:
|
|
108
106
|
code = self._FAILED_UNRECOVERABLE_CODE
|
|
109
|
-
self.log_error(f"
|
|
107
|
+
self.log_error(f"Closing with code {code}")
|
|
110
108
|
self._closed = True
|
|
111
109
|
sys.exit(code)
|
|
112
110
|
|
|
113
111
|
def close_success(self) -> None:
|
|
114
|
-
self.
|
|
115
|
-
f"Python4CPM.close_success: closing with code {self._SUCCESS_CODE}"
|
|
116
|
-
)
|
|
112
|
+
self.log_debug(f"Closing with code {self._SUCCESS_CODE}")
|
|
117
113
|
self._closed = True
|
|
118
114
|
sys.exit(self._SUCCESS_CODE)
|
|
119
115
|
|
|
120
116
|
def _on_exit(self):
|
|
121
117
|
if self._closed is False:
|
|
122
|
-
message = "
|
|
118
|
+
message = "No close signal called"
|
|
123
119
|
self.log_error(message)
|
|
124
120
|
sys.stderr.write(message)
|
|
125
121
|
sys.stderr.flush()
|
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python4cpm
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.24
|
|
4
4
|
Summary: Python for CPM
|
|
5
5
|
Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2026 Gonzalo Atienza Rela
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in
|
|
18
|
-
all copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
26
|
-
THE SOFTWARE.
|
|
27
|
-
|
|
6
|
+
License-Expression: MIT
|
|
28
7
|
Requires-Python: >=3.10
|
|
29
8
|
Description-Content-Type: text/markdown
|
|
30
9
|
License-File: LICENSE
|
|
@@ -32,7 +11,7 @@ Dynamic: license-file
|
|
|
32
11
|
|
|
33
12
|
# Python4CPM
|
|
34
13
|
|
|
35
|
-
A simple way of using python scripts with CyberArk CPM rotations. This module leverages the [Credential Management .NET SDK](https://docs.cyberark.com/privilege-cloud-standard/latest/en/content/pasimp/plug-in-netinvoker.htm) from CyberArk to securely offload a password rotation logic into a python script.
|
|
14
|
+
A simple way of using python scripts with CyberArk CPM/SRS rotations. This module leverages the [Credential Management .NET SDK](https://docs.cyberark.com/privilege-cloud-standard/latest/en/content/pasimp/plug-in-netinvoker.htm) from CyberArk to securely offload a password rotation logic into a python script.
|
|
36
15
|
|
|
37
16
|
This platform allows you to duplicate it multiple times, simply changing its settings from Privilege Cloud/PVWA to point to different python scripts leveraging the module `python4cpm`.
|
|
38
17
|
|
|
@@ -40,14 +19,14 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
40
19
|
|
|
41
20
|
### Preparing Python
|
|
42
21
|
|
|
43
|
-
1. Install Python
|
|
22
|
+
1. Install Python along CPM or the SRS Connector Management Agent.
|
|
44
23
|
- **Python must be installed for all users**. Follow the custom install steps from the installation wizard to check the checkbox.
|
|
45
|
-
3. Create a venv in
|
|
24
|
+
3. Create a venv in the server, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
|
|
46
25
|
4. Install `python4cpm` in your venv:
|
|
47
26
|
- If your CPM can connect to the internet, install with `c:\venv\Scripts\pip install python4cpm`.
|
|
48
27
|
- If your CPM cannot connect to the internet:
|
|
49
28
|
- Download the latest `python4cpm-*.whl` file from the [pypi project files](https://pypi.org/project/python4cpm/#files).
|
|
50
|
-
- Copy the file to
|
|
29
|
+
- Copy the file to the server into a temporary directory called `python4cpm-wheel`.
|
|
51
30
|
- From the parent directory of `python4cpm-wheel` run `c:\venv\Scripts\pip install --no-index --find-links=.\python4cpm-wheel python4cpm`.
|
|
52
31
|
|
|
53
32
|
|
|
@@ -84,13 +63,14 @@ This platform allows you to duplicate it multiple times, simply changing its set
|
|
|
84
63
|
from python4cpm import Python4CPMHandler
|
|
85
64
|
|
|
86
65
|
|
|
87
|
-
class
|
|
66
|
+
class MyRotator(Python4CPMHandler): # create a subclass for the Handler
|
|
88
67
|
"""
|
|
89
68
|
These are the usable properties and methods from Python4CPMHandler:
|
|
90
69
|
|
|
91
70
|
self.args.action # action requested from CPM/SRS
|
|
92
|
-
self.args.address # address from the account address field
|
|
93
71
|
self.args.username # username from the account username field
|
|
72
|
+
self.args.address # address from the account address field
|
|
73
|
+
self.args.port # port from the account port field
|
|
94
74
|
self.args.reconcile_username # reconcile username from the linked reconcile account
|
|
95
75
|
self.args.logon_username # logon username from the linked logon account
|
|
96
76
|
self.args.logging # used to carry the platform logging settings for python
|
|
@@ -101,13 +81,13 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
101
81
|
|
|
102
82
|
Logging methods -> Will only log if PythonLogging (platform parameters) is set to yes (default is yes)
|
|
103
83
|
|
|
104
|
-
self.log_error("this is an error message") # logs error into Logs/ThirdParty/
|
|
105
|
-
self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/
|
|
106
|
-
self.log_info("this is an info message") # logs info into Logs/ThirdParty/
|
|
84
|
+
self.log_error("this is an error message") # logs error into Logs/ThirdParty/MyRotator.log
|
|
85
|
+
self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/MyRotator.log
|
|
86
|
+
self.log_info("this is an info message") # logs info into Logs/ThirdParty/MyRotator.log
|
|
107
87
|
|
|
108
88
|
Logging level -> Will only log debug messages if PythonLoggingLevel (platform parameters) is set to debug (default is info)
|
|
109
89
|
|
|
110
|
-
self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/
|
|
90
|
+
self.log_debug("this is an debug message") # logs info into Logs/ThirdParty/MyRotator.log if logging level is set to debug
|
|
111
91
|
|
|
112
92
|
=============================
|
|
113
93
|
REQUIRED TERMINATION SIGNALS
|
|
@@ -152,7 +132,7 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
152
132
|
def _verify(self, from_reconcile=False):
|
|
153
133
|
if from_reconcile is False:
|
|
154
134
|
pass
|
|
155
|
-
# TODO: use self.args.address, self.args.
|
|
135
|
+
# TODO: use self.args.username, self.args.address, self.args.port, self.secrets.password.get()
|
|
156
136
|
# for your logic in a verification
|
|
157
137
|
else:
|
|
158
138
|
pass
|
|
@@ -160,42 +140,42 @@ class MyApp(Python4CPMHandler): # create a subclass for the Handler
|
|
|
160
140
|
# for your logic in a verification
|
|
161
141
|
result = True
|
|
162
142
|
if result is True:
|
|
163
|
-
self.log_info("verification successful") # logs info message into Logs/ThirdParty/
|
|
143
|
+
self.log_info("verification successful") # logs info message into Logs/ThirdParty/MyRotator.log
|
|
164
144
|
else:
|
|
165
|
-
self.log_error("something went wrong") # logs error message Logs/ThirdParty/
|
|
145
|
+
self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyRotator.log
|
|
166
146
|
self.close_fail()
|
|
167
147
|
|
|
168
148
|
def _change(self, from_reconcile=False):
|
|
169
149
|
if from_reconcile is False:
|
|
170
150
|
pass
|
|
171
|
-
# TODO: use self.args.address, self.args.
|
|
151
|
+
# TODO: use self.args.username, self.args.address, self.args.port, self.secrets.password.get()
|
|
172
152
|
# and self.secrets.new_password.get() for your logic in a rotation
|
|
173
153
|
else:
|
|
174
154
|
pass
|
|
175
|
-
# TODO: use self.args.address, self.args.
|
|
155
|
+
# TODO: use self.args.username, self.args.address, self.args.port, self.args.reconcile_username,
|
|
176
156
|
# self.secrets.reconcile_password.get() and self.secrets.new_password.get() for your logic in a reconciliation
|
|
177
157
|
result = True
|
|
178
158
|
if result is True:
|
|
179
|
-
self.log_info("rotation successful") # logs info message into Logs/ThirdParty/
|
|
159
|
+
self.log_info("rotation successful") # logs info message into Logs/ThirdParty/MyRotator.log
|
|
180
160
|
else:
|
|
181
|
-
self.log_error("something went wrong") # logs error message Logs/ThirdParty/
|
|
161
|
+
self.log_error("something went wrong") # logs error message Logs/ThirdParty/MyRotator.log
|
|
182
162
|
self.close_fail()
|
|
183
163
|
|
|
184
164
|
|
|
185
165
|
if __name__ == "__main__":
|
|
186
|
-
|
|
166
|
+
MyRotator().run() # initializes the class and calls the action that was requested from CPM/SRS.
|
|
187
167
|
```
|
|
188
168
|
(*) More realistic examples can be found [here](https://github.com/gonatienza/python4cpm/blob/main/examples/python4cpmhandler).
|
|
189
169
|
|
|
190
170
|
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
191
|
-
1. Verify -> the sciprt will be executed once
|
|
192
|
-
2. Change -> the sciprt will be executed twice,
|
|
193
|
-
- If
|
|
194
|
-
3. Reconcile -> the sciprt will be executed twice,
|
|
195
|
-
- If
|
|
196
|
-
4. When calling `
|
|
197
|
-
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return
|
|
198
|
-
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return
|
|
171
|
+
1. Verify -> the sciprt will be executed once running the `MyRotator.verify()` method.
|
|
172
|
+
2. Change -> the sciprt will be executed twice, running first the `MyRotator.logon()` method and secondly the `MyRotator.change()` method.
|
|
173
|
+
- If both actions are not terminated with `self.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `self.close_fail(unrecoverable=True)`.
|
|
174
|
+
3. Reconcile -> the sciprt will be executed twice, running first the `MyRotator.prereconcile()` method and secondly the `MyRotator.reconcile()` method.
|
|
175
|
+
- If both actions are not terminated with `self.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `self.close_fail(unrecoverable=True)`.
|
|
176
|
+
4. When calling `MyRotator.verify()`, `MyRotator.logon()` or `MyRotator.prereconcile()`: `self.secrets.new_password.get()` will always return an empty string.
|
|
177
|
+
5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return empty strings.
|
|
178
|
+
6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return empty strings.
|
|
199
179
|
|
|
200
180
|
|
|
201
181
|
### Using Python4CPM properties and methods directly (for low level controls):
|
|
@@ -208,8 +188,9 @@ p4cpm = Python4CPM("MyApp") # this instantiates the object and grabs all argumen
|
|
|
208
188
|
|
|
209
189
|
# These are the usable properties and related methods from the object:
|
|
210
190
|
p4cpm.args.action # action requested from CPM/SRS
|
|
211
|
-
p4cpm.args.address # address from the account address field
|
|
212
191
|
p4cpm.args.username # username from the account username field
|
|
192
|
+
p4cpm.args.address # address from the account address field
|
|
193
|
+
p4cpm.args.port # port from the account port field
|
|
213
194
|
p4cpm.args.reconcile_username # reconcile username from the linked reconcile account
|
|
214
195
|
p4cpm.args.logon_username # logon username from the linked logon account
|
|
215
196
|
p4cpm.args.logging # used to carry the platform logging settings for python
|
|
@@ -236,11 +217,11 @@ p4cpm.log_debug("this is an debug message") # logs info into Logs/ThirdParty/MyA
|
|
|
236
217
|
def verify(from_reconcile=False):
|
|
237
218
|
if from_reconcile is False:
|
|
238
219
|
pass
|
|
239
|
-
# TODO: use p4cpm.args.address, p4cpm.args.
|
|
220
|
+
# TODO: use p4cpm.args.username, p4cpm.args.address, p4cpm.args.port, p4cpm.secrets.password.get()
|
|
240
221
|
# for your logic in a verification
|
|
241
222
|
else:
|
|
242
223
|
pass
|
|
243
|
-
# TODO: use p4cpm.args.address, p4cpm.args.reconcile_username, p4cpm.secrets.reconcile_password.get()
|
|
224
|
+
# TODO: use p4cpm.args.address, p4cpm.args.port, p4cpm.args.reconcile_username, p4cpm.secrets.reconcile_password.get()
|
|
244
225
|
# for your logic in a verification
|
|
245
226
|
result = True
|
|
246
227
|
if result is True:
|
|
@@ -254,11 +235,11 @@ def verify(from_reconcile=False):
|
|
|
254
235
|
def change(from_reconcile=False):
|
|
255
236
|
if from_reconcile is False:
|
|
256
237
|
pass
|
|
257
|
-
# TODO: use p4cpm.args.address, p4cpm.args.
|
|
238
|
+
# TODO: use p4cpm.args.username, p4cpm.args.address, p4cpm.args.port, p4cpm.secrets.password.get()
|
|
258
239
|
# and p4cpm.secrets.new_password.get() for your logic in a rotation
|
|
259
240
|
else:
|
|
260
241
|
pass
|
|
261
|
-
# TODO: use p4cpm.args.address, p4cpm.args.
|
|
242
|
+
# TODO: use p4cpm.args.username, p4cpm.args.address, p4cpm.args.port, p4cpm.args.reconcile_username,
|
|
262
243
|
# p4cpm.secrets.reconcile_password.get() and p4cpm.secrets.new_password.get() for your logic in a reconciliation
|
|
263
244
|
result = True
|
|
264
245
|
if result is True:
|
|
@@ -299,12 +280,12 @@ if __name__ == "__main__":
|
|
|
299
280
|
When doing `verify`, `change` or `reconcile` from Privilege Cloud/PVWA:
|
|
300
281
|
1. Verify -> the sciprt will be executed once with the `p4cpm.args.action` as `Python4CPM.ACTION_VERIFY`.
|
|
301
282
|
2. Change -> the sciprt will be executed twice, once with the action `p4cpm.args.action` as `Python4CPM.ACTION_LOGON` and once as `Python4CPM.ACTION_CHANGE`.
|
|
302
|
-
- If
|
|
283
|
+
- If both actions are not terminated with `p4cpm.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `p4cpm.close_fail(unrecoverable=True)`.
|
|
303
284
|
3. Reconcile -> the sciprt will be executed twice, once with the `p4cpm.args.action` as `Python4CPM.ACTION_PRERECONCILE` and once as `Python4CPM.ACTION_RECONCILE`.
|
|
304
|
-
- If
|
|
285
|
+
- If both actions are not terminated with `p4cpm.close_success()` and the scripts terminates without any exception, CPM/SRS will see this as a `p4cpm.close_fail(unrecoverable=True)`.
|
|
305
286
|
4. When `p4cpm.args.action` comes as `Python4CPM.ACTION_VERIFY`, `Python4CPM.ACTION_LOGON` or `Python4CPM.ACTION_PRERECONCILE`: `p4cpm.secrets.new_password.get()` will always return an empty string.
|
|
306
|
-
5. If a logon account is not linked, `p4cpm.args.logon_username` and `p4cpm.secrets.logon_password.get()` will return
|
|
307
|
-
6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return
|
|
287
|
+
5. If a logon account is not linked, `p4cpm.args.logon_username` and `p4cpm.secrets.logon_password.get()` will return empty strings.
|
|
288
|
+
6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return empty strings.
|
|
308
289
|
|
|
309
290
|
|
|
310
291
|
### Installing dependencies in python venv
|
|
@@ -329,6 +310,8 @@ pip install python4cpm
|
|
|
329
310
|
|
|
330
311
|
### Example:
|
|
331
312
|
|
|
313
|
+
#### Set your arguments and secrets:
|
|
314
|
+
|
|
332
315
|
```python
|
|
333
316
|
from python4cpm import NETHelper, Python4CPM, Python4CPMHandler
|
|
334
317
|
from getpass import getpass
|
|
@@ -339,15 +322,12 @@ password = getpass("password: ") # password from account
|
|
|
339
322
|
logon_password = getpass("logon_password: ") # password from linked logon account
|
|
340
323
|
reconcile_password = getpass("reconcile_password: ") # password from linked reconcile account
|
|
341
324
|
new_password = getpass("new_password: ") # new password for the rotation
|
|
342
|
-
```
|
|
343
325
|
|
|
344
|
-
#### Set your arguments and secrets:
|
|
345
|
-
|
|
346
|
-
```python
|
|
347
326
|
NETHelper.set(
|
|
348
327
|
action=Python4CPM.ACTION_LOGON, # use actions from Python4CPM.ACTION_*
|
|
349
|
-
address="myapp.corp.local", # populate with the address from your account properties
|
|
350
328
|
username="jdoe", # populate with the username from your account properties
|
|
329
|
+
address="myapp.corp.local", # populate with the address from your account properties
|
|
330
|
+
port="8443", # populate with the port from your account properties
|
|
351
331
|
logon_username="ldoe", # populate with the logon account username from your linked logon account
|
|
352
332
|
reconcile_username="rdoe", # ppopulate with the reconcile account username from your linked logon account
|
|
353
333
|
logging="yes", # populate with the PythonLogging parameter from the platform: "yes" or "no"
|
|
@@ -362,7 +342,7 @@ NETHelper.set(
|
|
|
362
342
|
#### Using the handler (recommended):
|
|
363
343
|
|
|
364
344
|
```python
|
|
365
|
-
class
|
|
345
|
+
class MyRotator(Python4CPMHandler):
|
|
366
346
|
def verify(self):
|
|
367
347
|
# TODO: Add your logic here
|
|
368
348
|
self.close_success()
|
|
@@ -383,7 +363,7 @@ class MyApp(Python4CPMHandler):
|
|
|
383
363
|
# TODO: Add your logic here
|
|
384
364
|
self.close_success()
|
|
385
365
|
|
|
386
|
-
|
|
366
|
+
MyRotator().run()
|
|
387
367
|
```
|
|
388
368
|
|
|
389
369
|
#### Using Python4CPM properties and methods directly:
|
|
@@ -399,6 +379,7 @@ p4cpm.close_success()
|
|
|
399
379
|
|
|
400
380
|
#### Remember for your final script:
|
|
401
381
|
|
|
402
|
-
- Change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")` if you are using the properties and methods directly.
|
|
403
|
-
- Remove any secrets prompting or interactive interruptions.
|
|
404
382
|
- Remove the import of `NETHelper`.
|
|
383
|
+
- Remove the `NETHelper.set()` call.
|
|
384
|
+
- If applicable, change the definition of `p4cpm` from `p4cpm = NETHelper.get()` to `p4cpm = Python4CPM("MyApp")`.
|
|
385
|
+
- Remove any secrets prompting or interactive interruptions.
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import logging
|
|
3
|
-
from logging.handlers import RotatingFileHandler
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
_LOGS_DIR = os.path.join("Logs", "ThirdParty")
|
|
7
|
-
_LOGGING_ENABLED_VALUE = "yes"
|
|
8
|
-
_LOGGING_LEVELS = {
|
|
9
|
-
"info": logging.INFO,
|
|
10
|
-
"debug": logging.DEBUG
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def get_logger(
|
|
15
|
-
name: str,
|
|
16
|
-
args_logging: str,
|
|
17
|
-
args_logging_level: str
|
|
18
|
-
) -> logging.Logger:
|
|
19
|
-
if args_logging.lower() != _LOGGING_ENABLED_VALUE:
|
|
20
|
-
return None
|
|
21
|
-
os.makedirs(_LOGS_DIR, exist_ok=True)
|
|
22
|
-
logs_file = os.path.join(_LOGS_DIR, f"{__name__}-{name}.log")
|
|
23
|
-
_id = os.urandom(4).hex()
|
|
24
|
-
logger = logging.getLogger(_id)
|
|
25
|
-
if args_logging_level.lower() in _LOGGING_LEVELS:
|
|
26
|
-
logger.setLevel(_LOGGING_LEVELS[args_logging_level.lower()])
|
|
27
|
-
else:
|
|
28
|
-
logger.setLevel(_LOGGING_LEVELS["info"])
|
|
29
|
-
handler = RotatingFileHandler(
|
|
30
|
-
filename=logs_file,
|
|
31
|
-
maxBytes=1024 ** 2,
|
|
32
|
-
backupCount=1
|
|
33
|
-
)
|
|
34
|
-
formatter = logging.Formatter(
|
|
35
|
-
fmt="%(asctime)s | %(name)s | %(levelname)s | %(message)s",
|
|
36
|
-
datefmt="%Y-%m-%d %H:%M:%S"
|
|
37
|
-
)
|
|
38
|
-
handler.setFormatter(formatter)
|
|
39
|
-
logger.addHandler(handler)
|
|
40
|
-
return logger
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|