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.
@@ -1,30 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python4cpm
3
- Version: 1.0.22
3
+ Version: 1.0.24
4
4
  Summary: Python for CPM
5
5
  Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
6
- License: MIT License
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 in CPM.
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 CPM, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
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 CPM and extract to a temporary directory called `python4cpm-wheel`.
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 MyApp(Python4CPMHandler): # create a subclass for the Handler
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/MyApp.log
105
- self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/MyApp.log
106
- self.log_info("this is an info message") # logs info into Logs/ThirdParty/MyApp.log
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/MyApp.log if logging level is set to debug
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.username, self.secrets.password.get()
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/MyApp.log
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/MyApp.log
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.username, self.secrets.password.get()
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.username, self.args.reconcile_username,
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/MyApp.log
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/MyApp.log
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
- MyApp().run() # initializes the class and calls the action that was requested from CPM/SRS.
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 with the `MyApp.verify()` method.
192
- 2. Change -> the sciprt will be executed twice, once with the action `MyApp.logon()` method and once as `MyApp.change()` method.
193
- - If all 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)`.
194
- 3. Reconcile -> the sciprt will be executed twice, once with the action `MyApp.prereconcile()` method and once as `MyApp.reconcile()` method.
195
- - If all 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)`.
196
- 4. When calling `MyApp.logon()` or `MyApp.prereconcile()`: `self.secrets.new_password.get()` will always return an empty string.
197
- 5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return an empty string.
198
- 6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return an empty string.
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.username, p4cpm.secrets.password.get()
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.username, p4cpm.secrets.password.get()
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.username, p4cpm.args.reconcile_username,
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 all 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)`.
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 all 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)`.
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 an empty string.
307
- 6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return an empty string.
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 MyApp(Python4CPMHandler):
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
- MyApp().run()
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 in CPM.
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 CPM, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
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 CPM and extract to a temporary directory called `python4cpm-wheel`.
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 MyApp(Python4CPMHandler): # create a subclass for the Handler
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/MyApp.log
73
- self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/MyApp.log
74
- self.log_info("this is an info message") # logs info into Logs/ThirdParty/MyApp.log
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/MyApp.log if logging level is set to debug
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.username, self.secrets.password.get()
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/MyApp.log
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/MyApp.log
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.username, self.secrets.password.get()
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.username, self.args.reconcile_username,
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/MyApp.log
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/MyApp.log
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
- MyApp().run() # initializes the class and calls the action that was requested from CPM/SRS.
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 with the `MyApp.verify()` method.
160
- 2. Change -> the sciprt will be executed twice, once with the action `MyApp.logon()` method and once as `MyApp.change()` method.
161
- - If all 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)`.
162
- 3. Reconcile -> the sciprt will be executed twice, once with the action `MyApp.prereconcile()` method and once as `MyApp.reconcile()` method.
163
- - If all 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)`.
164
- 4. When calling `MyApp.logon()` or `MyApp.prereconcile()`: `self.secrets.new_password.get()` will always return an empty string.
165
- 5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return an empty string.
166
- 6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return an empty string.
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.username, p4cpm.secrets.password.get()
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.username, p4cpm.secrets.password.get()
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.username, p4cpm.args.reconcile_username,
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 all 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)`.
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 all 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)`.
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 an empty string.
275
- 6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return an empty string.
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 MyApp(Python4CPMHandler):
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
- MyApp().run()
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.22"
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 = { file = "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 username(self) -> str:
40
- return self._username
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 get_logger
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.log_info("Python4CPM.__init__: initiating...")
36
- self._log_args()
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 _log_args(self) -> None:
97
- for key, value in vars(self._args).items():
98
- common_message = f"Python4CPM._log_args: {key.strip('_')} ->"
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
- self.log_info(f"{common_message} {value}")
94
+ if masked:
95
+ logging_value = "[SET]"
96
+ else:
97
+ logging_value = value
101
98
  else:
102
- self.log_info(f"{common_message} [NOT SET]")
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"Python4CPM.close_fail: closing with code {code}")
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.log_info(
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 = "Python4CPM._on_exit: no close signal called"
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.22
3
+ Version: 1.0.24
4
4
  Summary: Python for CPM
5
5
  Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
6
- License: MIT License
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 in CPM.
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 CPM, by running `py -m venv c:\venv`. If desired, use a custom location and adjust any future references.
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 CPM and extract to a temporary directory called `python4cpm-wheel`.
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 MyApp(Python4CPMHandler): # create a subclass for the Handler
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/MyApp.log
105
- self.log_warning("this is a warning message") # logs warning into Logs/ThirdParty/MyApp.log
106
- self.log_info("this is an info message") # logs info into Logs/ThirdParty/MyApp.log
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/MyApp.log if logging level is set to debug
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.username, self.secrets.password.get()
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/MyApp.log
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/MyApp.log
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.username, self.secrets.password.get()
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.username, self.args.reconcile_username,
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/MyApp.log
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/MyApp.log
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
- MyApp().run() # initializes the class and calls the action that was requested from CPM/SRS.
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 with the `MyApp.verify()` method.
192
- 2. Change -> the sciprt will be executed twice, once with the action `MyApp.logon()` method and once as `MyApp.change()` method.
193
- - If all 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)`.
194
- 3. Reconcile -> the sciprt will be executed twice, once with the action `MyApp.prereconcile()` method and once as `MyApp.reconcile()` method.
195
- - If all 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)`.
196
- 4. When calling `MyApp.logon()` or `MyApp.prereconcile()`: `self.secrets.new_password.get()` will always return an empty string.
197
- 5. If a logon account is not linked, `self.args.logon_username` and `self.secrets.logon_password.get()` will return an empty string.
198
- 6. If a reconcile account is not linked, `self.args.reconcile_username` and `self.secrets.reconcile_password.get()` will return an empty string.
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.username, p4cpm.secrets.password.get()
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.username, p4cpm.secrets.password.get()
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.username, p4cpm.args.reconcile_username,
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 all 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)`.
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 all 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)`.
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 an empty string.
307
- 6. If a reconcile account is not linked, `p4cpm.args.reconcile_username` and `p4cpm.secrets.reconcile_password.get()` will return an empty string.
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 MyApp(Python4CPMHandler):
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
- MyApp().run()
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