python4cpm 1.0.14__tar.gz → 1.0.16__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python4cpm
3
- Version: 1.0.14
3
+ Version: 1.0.16
4
4
  Summary: Python for CPM
5
5
  Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
6
6
  License: MIT License
@@ -32,9 +32,9 @@ Dynamic: license-file
32
32
 
33
33
  # Python4CPM
34
34
 
35
- A simple way of using python scripts with CyberArk CPM rotations. This module levereages the [Terminal Plugin Controller](https://docs.cyberark.com/pam-self-hosted/latest/en/content/pasimp/plug-in-terminal-plugin-controller.htm) (TPC) in CPM to offload a password rotation logic into a script.
35
+ A simple way of using python scripts with CyberArk CPM rotations. This module levereages 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
36
 
37
- 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 included with the base platform.
37
+ 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
38
 
39
39
  ## Installation
40
40
 
@@ -45,22 +45,23 @@ This platform allows you to duplicate it multiple times, simply changing its set
45
45
  3. Install `python4cpm` in your venv:
46
46
  - If your CPM can connect to the internet, install with `c:\venv\Scripts\pip install python4cpm`.
47
47
  - If your CPM cannot connect to the internet:
48
- - Download the [latest wheel](https://github.com/gonatienza/python4cpm/releases/download/latest/python4cpm-wheel.zip).
48
+ - Download the `python4cpm-wheel.zip` asset from the latest [release](https://github.com/gonatienza/python4cpm/releases).
49
49
  - Copy the file to CPM and extract to a temporary location.
50
50
  - From the temporary location run `c:\venv\Scripts\pip install --no-index --find-links=.\python4cpm-wheel python4cpm`.
51
51
 
52
52
 
53
53
  ### Importing the platform
54
54
 
55
- 1. Download the [latest platform zip file](https://github.com/gonatienza/python4cpm/releases/download/latest/python4cpm-platform.zip).
56
- 2. Import the platform zip file into Privilege Cloud/PVWA `(Administration -> Platform Management -> Import platform)`.
57
- 3. Craft your python script and place it within the bin folder of CPM (`C:\Program Files (x86)\CyberArk\Password Manager\bin`).
58
- 4. Duplicate the imported platform in Privilege Cloud/PVWA `(Administration -> Platform Management -> Application -> Python for CPM)` and name it after your application (e.g., My App).
59
- 5. Edit the duplicated platform and specify the path of your placed script in the bin folder of CPM, under `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonScriptPath -> Value` (e.g., `bin\myapp.py`).
60
- 6. If you used a custom venv location, also update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonExePath -> Value` with the custom path for the venv's `python.exe` file (e.g., `c:\my-venv-path\Scripts\python.exe`).
61
- 7. If you want to disable logging, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLogging -> Value` to `no`.
62
- 8. If you want to change the logging level to `debug`, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLoggingLevel -> Value` to `debug`.
63
- 9. For new applications repeat steps from 3 to 8.
55
+ 1. Download the latest [Credential Management .NET SDK](https://community.cyberark.com/marketplace/s/#a3550000000EkA0AAK-a3950000000jjoOAAQ) and place its content in the bin folder of CPM (`C:\Program Files (x86)\CyberArk\Password Manager\bin`).
56
+ 2. Download the `python4cpm-platform.zip` asset from the [release](https://github.com/gonatienza/python4cpm/releases).
57
+ 3. Import the platform zip file into Privilege Cloud/PVWA `(Administration -> Platform Management -> Import platform)`.
58
+ 4. Craft your python script and place it within the bin folder of CPM (`C:\Program Files (x86)\CyberArk\Password Manager\bin`).
59
+ 5. Duplicate the imported platform in Privilege Cloud/PVWA `(Administration -> Platform Management -> Application -> Python for CPM)` and name it after your application (e.g., My App).
60
+ 6. Edit the duplicated platform and specify the path of your placed script in the bin folder of CPM, under `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonScriptPath -> Value` (e.g., `bin\myapp.py`).
61
+ 7. If you used a custom venv location, also update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonExePath -> Value` with the custom path for the venv's `python.exe` file (e.g., `c:\my-venv-path\Scripts\python.exe`).
62
+ 8. If you want to disable logging, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLogging -> Value` to `no`.
63
+ 9. If you want to change the logging level to `debug`, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLoggingLevel -> Value` to `debug`.
64
+ 10. For new applications repeat steps from 4 to 9.
64
65
 
65
66
 
66
67
  ## Python Script
@@ -137,7 +138,7 @@ def change(from_reconcile=False):
137
138
 
138
139
  if __name__ == "__main__":
139
140
  try:
140
- if action == Python4CPM.ACTION_VERIFY: # class attribute ACTION_VERIFY holds the verify action value
141
+ if p4cpm.args.action == Python4CPM.ACTION_VERIFY: # class attribute ACTION_VERIFY holds the verify action value
141
142
  verify()
142
143
  p4cpm.close_success() # terminate with success state
143
144
  elif p4cpm.args.action == Python4CPM.ACTION_LOGON: # class attribute ACTION_LOGON holds the logon action value
@@ -159,7 +160,7 @@ if __name__ == "__main__":
159
160
  ## p4cpm.log_error("reconciliation is not supported") # let the logs know that reconciliation is not supported
160
161
  ## p4cpm.close_fail() # let CPM know to check the logs
161
162
  else:
162
- p4cpm.log_error(f"invalid action: '{action}'") # logs into Logs/ThirdParty/Python4CPM/MyApp.log
163
+ p4cpm.log_error(f"invalid action: '{p4cpm.args.action}'") # logs into Logs/ThirdParty/Python4CPM/MyApp.log
163
164
  p4cpm.close_fail(unrecoverable=True) # terminate with unrecoverable failed state
164
165
  except Exception as e:
165
166
  p4cpm.log_error(f"{type(e).__name__}: {e}")
@@ -189,14 +190,15 @@ As with any python venv, you can install dependancies in your venv.
189
190
 
190
191
  ## Dev Helper:
191
192
 
192
- TPC is a binary Terminal Plugin Controller in CPM. It passes information to Python4CPM through arguments and prompts when calling the script.
193
- For dev purposes, `TPCHelper` is a companion helper that simplifies the instantiation of the `Python4CPM` object by simulating how TPC passes those arguments and prompts.
193
+ For dev purposes, `NETHelper` is a companion helper that simplifies the instantiation of the `Python4CPM` object by simulating how the plugin passes arguments and secrets to `Python4CPM`.
194
194
  Install this module (in a dev workstation) with:
195
195
 
196
196
  ```bash
197
197
  pip install python4cpm
198
198
  ```
199
199
 
200
+ **Note**: As CPM runs in Windows, the plugin was built to pass secrets securely to the `Python4CPM.crypto` module using the Data Protection API (DPAPI). For dev purposes in Linux/Mac dev workstations, those secrets will appear as plaintext in the environment of the process. This is informational only, the module will use its encryption/decryption capabilities automatically in Windows and you do not have to do anything specific to enable it.
201
+
200
202
  ### Example:
201
203
 
202
204
  ```python
@@ -1,8 +1,8 @@
1
1
  # Python4CPM
2
2
 
3
- A simple way of using python scripts with CyberArk CPM rotations. This module levereages the [Terminal Plugin Controller](https://docs.cyberark.com/pam-self-hosted/latest/en/content/pasimp/plug-in-terminal-plugin-controller.htm) (TPC) in CPM to offload a password rotation logic into a script.
3
+ A simple way of using python scripts with CyberArk CPM rotations. This module levereages 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
- 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 included with the base platform.
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
 
7
7
  ## Installation
8
8
 
@@ -13,22 +13,23 @@ This platform allows you to duplicate it multiple times, simply changing its set
13
13
  3. Install `python4cpm` in your venv:
14
14
  - If your CPM can connect to the internet, install with `c:\venv\Scripts\pip install python4cpm`.
15
15
  - If your CPM cannot connect to the internet:
16
- - Download the [latest wheel](https://github.com/gonatienza/python4cpm/releases/download/latest/python4cpm-wheel.zip).
16
+ - Download the `python4cpm-wheel.zip` asset from the latest [release](https://github.com/gonatienza/python4cpm/releases).
17
17
  - Copy the file to CPM and extract to a temporary location.
18
18
  - From the temporary location run `c:\venv\Scripts\pip install --no-index --find-links=.\python4cpm-wheel python4cpm`.
19
19
 
20
20
 
21
21
  ### Importing the platform
22
22
 
23
- 1. Download the [latest platform zip file](https://github.com/gonatienza/python4cpm/releases/download/latest/python4cpm-platform.zip).
24
- 2. Import the platform zip file into Privilege Cloud/PVWA `(Administration -> Platform Management -> Import platform)`.
25
- 3. Craft your python script and place it within the bin folder of CPM (`C:\Program Files (x86)\CyberArk\Password Manager\bin`).
26
- 4. Duplicate the imported platform in Privilege Cloud/PVWA `(Administration -> Platform Management -> Application -> Python for CPM)` and name it after your application (e.g., My App).
27
- 5. Edit the duplicated platform and specify the path of your placed script in the bin folder of CPM, under `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonScriptPath -> Value` (e.g., `bin\myapp.py`).
28
- 6. If you used a custom venv location, also update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonExePath -> Value` with the custom path for the venv's `python.exe` file (e.g., `c:\my-venv-path\Scripts\python.exe`).
29
- 7. If you want to disable logging, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLogging -> Value` to `no`.
30
- 8. If you want to change the logging level to `debug`, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLoggingLevel -> Value` to `debug`.
31
- 9. For new applications repeat steps from 3 to 8.
23
+ 1. Download the latest [Credential Management .NET SDK](https://community.cyberark.com/marketplace/s/#a3550000000EkA0AAK-a3950000000jjoOAAQ) and place its content in the bin folder of CPM (`C:\Program Files (x86)\CyberArk\Password Manager\bin`).
24
+ 2. Download the `python4cpm-platform.zip` asset from the [release](https://github.com/gonatienza/python4cpm/releases).
25
+ 3. Import the platform zip file into Privilege Cloud/PVWA `(Administration -> Platform Management -> Import platform)`.
26
+ 4. Craft your python script and place it within the bin folder of CPM (`C:\Program Files (x86)\CyberArk\Password Manager\bin`).
27
+ 5. Duplicate the imported platform in Privilege Cloud/PVWA `(Administration -> Platform Management -> Application -> Python for CPM)` and name it after your application (e.g., My App).
28
+ 6. Edit the duplicated platform and specify the path of your placed script in the bin folder of CPM, under `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonScriptPath -> Value` (e.g., `bin\myapp.py`).
29
+ 7. If you used a custom venv location, also update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonExePath -> Value` with the custom path for the venv's `python.exe` file (e.g., `c:\my-venv-path\Scripts\python.exe`).
30
+ 8. If you want to disable logging, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLogging -> Value` to `no`.
31
+ 9. If you want to change the logging level to `debug`, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLoggingLevel -> Value` to `debug`.
32
+ 10. For new applications repeat steps from 4 to 9.
32
33
 
33
34
 
34
35
  ## Python Script
@@ -105,7 +106,7 @@ def change(from_reconcile=False):
105
106
 
106
107
  if __name__ == "__main__":
107
108
  try:
108
- if action == Python4CPM.ACTION_VERIFY: # class attribute ACTION_VERIFY holds the verify action value
109
+ if p4cpm.args.action == Python4CPM.ACTION_VERIFY: # class attribute ACTION_VERIFY holds the verify action value
109
110
  verify()
110
111
  p4cpm.close_success() # terminate with success state
111
112
  elif p4cpm.args.action == Python4CPM.ACTION_LOGON: # class attribute ACTION_LOGON holds the logon action value
@@ -127,7 +128,7 @@ if __name__ == "__main__":
127
128
  ## p4cpm.log_error("reconciliation is not supported") # let the logs know that reconciliation is not supported
128
129
  ## p4cpm.close_fail() # let CPM know to check the logs
129
130
  else:
130
- p4cpm.log_error(f"invalid action: '{action}'") # logs into Logs/ThirdParty/Python4CPM/MyApp.log
131
+ p4cpm.log_error(f"invalid action: '{p4cpm.args.action}'") # logs into Logs/ThirdParty/Python4CPM/MyApp.log
131
132
  p4cpm.close_fail(unrecoverable=True) # terminate with unrecoverable failed state
132
133
  except Exception as e:
133
134
  p4cpm.log_error(f"{type(e).__name__}: {e}")
@@ -157,14 +158,15 @@ As with any python venv, you can install dependancies in your venv.
157
158
 
158
159
  ## Dev Helper:
159
160
 
160
- TPC is a binary Terminal Plugin Controller in CPM. It passes information to Python4CPM through arguments and prompts when calling the script.
161
- For dev purposes, `TPCHelper` is a companion helper that simplifies the instantiation of the `Python4CPM` object by simulating how TPC passes those arguments and prompts.
161
+ For dev purposes, `NETHelper` is a companion helper that simplifies the instantiation of the `Python4CPM` object by simulating how the plugin passes arguments and secrets to `Python4CPM`.
162
162
  Install this module (in a dev workstation) with:
163
163
 
164
164
  ```bash
165
165
  pip install python4cpm
166
166
  ```
167
167
 
168
+ **Note**: As CPM runs in Windows, the plugin was built to pass secrets securely to the `Python4CPM.crypto` module using the Data Protection API (DPAPI). For dev purposes in Linux/Mac dev workstations, those secrets will appear as plaintext in the environment of the process. This is informational only, the module will use its encryption/decryption capabilities automatically in Windows and you do not have to do anything specific to enable it.
169
+
168
170
  ### Example:
169
171
 
170
172
  ```python
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python4cpm"
7
- version = "1.0.14"
7
+ version = "1.0.16"
8
8
  description = "Python for CPM"
9
9
  authors = [
10
10
  { name = "Gonzalo Atienza Rela", email = "gonatienza@gmail.com" }
@@ -0,0 +1,17 @@
1
+ from python4cpm.python4cpm import Python4CPM
2
+ from python4cpm.args import Args
3
+ from python4cpm.secrets import Secret, Secrets
4
+ from python4cpm.crypto import Crypto
5
+ from python4cpm.nethelper import NETHelper
6
+ from importlib.metadata import version as __version
7
+
8
+ __version__ = __version(__name__)
9
+
10
+ __all__ = [
11
+ Args,
12
+ Secret,
13
+ Secrets,
14
+ Python4CPM,
15
+ Crypto,
16
+ NETHelper
17
+ ]
@@ -0,0 +1,56 @@
1
+ class Args:
2
+ ARGS = (
3
+ "action",
4
+ "address",
5
+ "username",
6
+ "logon_username",
7
+ "reconcile_username",
8
+ "logging",
9
+ "logging_level"
10
+ )
11
+
12
+ def __init__(
13
+ self: str,
14
+ action: str,
15
+ address: str,
16
+ username: str,
17
+ reconcile_username: str,
18
+ logon_username: str,
19
+ logging: str,
20
+ logging_level: str
21
+ ) -> None:
22
+ self._action = action
23
+ self._address = address
24
+ self._username = username
25
+ self._reconcile_username = reconcile_username
26
+ self._logon_username = logon_username
27
+ self._logging = logging
28
+ self._logging_level = logging_level
29
+
30
+ @property
31
+ def action(self) -> str:
32
+ return self._action
33
+
34
+ @property
35
+ def address(self) -> str:
36
+ return self._address
37
+
38
+ @property
39
+ def username(self) -> str:
40
+ return self._username
41
+
42
+ @property
43
+ def reconcile_username(self) -> str:
44
+ return self._reconcile_username
45
+
46
+ @property
47
+ def logon_username(self) -> str:
48
+ return self._logon_username
49
+
50
+ @property
51
+ def logging(self) -> str:
52
+ return self._logging
53
+
54
+ @property
55
+ def logging_level(self) -> str:
56
+ return self._logging_level
@@ -0,0 +1,74 @@
1
+ import sys
2
+ import ctypes
3
+ import ctypes.wintypes
4
+ import base64
5
+
6
+
7
+ class Crypto:
8
+ if sys.platform == "win32":
9
+ ENABLED = True
10
+ else:
11
+ ENABLED = False
12
+
13
+ class DataBlob(ctypes.Structure):
14
+ _fields_ = [
15
+ ("cbData", ctypes.wintypes.DWORD),
16
+ ("pbData", ctypes.POINTER(ctypes.c_ubyte))
17
+ ]
18
+
19
+ @classmethod
20
+ def _verify_enabled(cls):
21
+ if not cls.ENABLED:
22
+ raise OSError("DPAPI is only available on Windows")
23
+
24
+ @classmethod
25
+ def decrypt(cls, base64_enc_string: str) -> str:
26
+ cls._verify_enabled()
27
+ encrypted_bytes = base64.b64decode(base64_enc_string.encode())
28
+ buffer = ctypes.create_string_buffer(encrypted_bytes)
29
+ input_blob = cls.DataBlob(
30
+ len(encrypted_bytes),
31
+ ctypes.cast(buffer, ctypes.POINTER(ctypes.c_ubyte))
32
+ )
33
+ output_blob = cls.DataBlob()
34
+ crypt_res = ctypes.windll.crypt32.CryptUnprotectData(
35
+ ctypes.byref(input_blob),
36
+ None,
37
+ None,
38
+ None,
39
+ None,
40
+ 0,
41
+ ctypes.byref(output_blob)
42
+ )
43
+ if crypt_res:
44
+ plaintext = ctypes.string_at(output_blob.pbData, output_blob.cbData)
45
+ ctypes.windll.kernel32.LocalFree(output_blob.pbData)
46
+ return plaintext.decode("utf-16-le")
47
+ else:
48
+ raise ctypes.WinError()
49
+
50
+ @classmethod
51
+ def encrypt(cls, plaintext: str) -> str:
52
+ cls._verify_enabled()
53
+ plain_bytes = plaintext.encode("utf-16-le")
54
+ buffer = ctypes.create_string_buffer(plain_bytes)
55
+ input_blob = cls.DataBlob(
56
+ len(plain_bytes),
57
+ ctypes.cast(buffer, ctypes.POINTER(ctypes.c_ubyte))
58
+ )
59
+ output_blob = cls.DataBlob()
60
+ crypt_res = ctypes.windll.crypt32.CryptProtectData(
61
+ ctypes.byref(input_blob),
62
+ None,
63
+ None,
64
+ None,
65
+ None,
66
+ 0,
67
+ ctypes.byref(output_blob)
68
+ )
69
+ if crypt_res:
70
+ encrypted = ctypes.string_at(output_blob.pbData, output_blob.cbData)
71
+ ctypes.windll.kernel32.LocalFree(output_blob.pbData)
72
+ return base64.b64encode(encrypted).decode()
73
+ else:
74
+ raise ctypes.WinError()
@@ -0,0 +1,46 @@
1
+ import os
2
+ import logging
3
+ from logging.handlers import RotatingFileHandler
4
+
5
+
6
+ _LOGS_DIR = os.path.join("Logs", "ThirdParty", "Python4CPM")
7
+ _CPM_ROOT_DIR = "C:\\Program Files (x86)\\CyberArk\\Password Manager"
8
+ if os.path.exists(_CPM_ROOT_DIR):
9
+ _LOGS_DIR = os.path.join(_CPM_ROOT_DIR, _LOGS_DIR)
10
+ _LOGGING_ENABLED_VALUE = "yes"
11
+ _LOGGING_LEVELS = {
12
+ "info": logging.INFO,
13
+ "debug": logging.DEBUG
14
+ }
15
+
16
+
17
+ def get_logger(
18
+ name: str,
19
+ args_logging: str,
20
+ args_logging_level: str
21
+ ) -> logging.Logger:
22
+ if args_logging is None:
23
+ return None
24
+ if args_logging.lower() != _LOGGING_ENABLED_VALUE:
25
+ return None
26
+ os.makedirs(_LOGS_DIR, exist_ok=True)
27
+ logs_file = os.path.join(_LOGS_DIR, f"{name}.log")
28
+ _id = os.urandom(4).hex()
29
+ logger = logging.getLogger(_id)
30
+ logging_level = args_logging_level.lower()
31
+ if logging_level in _LOGGING_LEVELS:
32
+ logger.setLevel(_LOGGING_LEVELS[logging_level])
33
+ else:
34
+ logger.setLevel(_LOGGING_LEVELS["info"])
35
+ handler = RotatingFileHandler(
36
+ filename=logs_file,
37
+ maxBytes=1 * 1024 * 1024,
38
+ backupCount=1
39
+ )
40
+ formatter = logging.Formatter(
41
+ fmt="%(asctime)s | %(name)s | %(levelname)s | %(message)s",
42
+ datefmt="%Y-%m-%d %H:%M:%S"
43
+ )
44
+ handler.setFormatter(formatter)
45
+ logger.addHandler(handler)
46
+ return logger
@@ -0,0 +1,47 @@
1
+ from python4cpm.python4cpm import Python4CPM
2
+ from python4cpm.args import Args
3
+ from python4cpm.secrets import Secrets
4
+ from python4cpm.crypto import Crypto
5
+ import os
6
+
7
+
8
+ class NETHelper:
9
+ @classmethod
10
+ def run(
11
+ cls,
12
+ action: str = "",
13
+ address: str = "",
14
+ username: str = "",
15
+ logon_username: str = "",
16
+ reconcile_username: str = "",
17
+ logging: str = "",
18
+ logging_level: str = "",
19
+ password: str = "",
20
+ logon_password: str = "",
21
+ reconcile_password: str = "",
22
+ new_password: str = ""
23
+ ) -> Python4CPM:
24
+ _args = [
25
+ action,
26
+ address,
27
+ username,
28
+ logon_username,
29
+ reconcile_username,
30
+ logging,
31
+ logging_level
32
+ ]
33
+ for i, arg in enumerate(Args.ARGS):
34
+ os.environ[f"PYTHON4CPM_{arg.upper()}"] = _args[i]
35
+ _secrets = [
36
+ password,
37
+ logon_password,
38
+ reconcile_password,
39
+ new_password
40
+ ]
41
+ for i, secret in enumerate(Secrets.SECRETS):
42
+ if Crypto.ENABLED:
43
+ _secret = Crypto.encrypt(_secrets[i])
44
+ else:
45
+ _secret = _secrets[i]
46
+ os.environ[f"PYTHON4CPM_{secret.upper()}"] = _secret
47
+ return Python4CPM(cls.__name__)
@@ -0,0 +1,118 @@
1
+ import os
2
+ import sys
3
+ import logging
4
+ from python4cpm.secrets import Secrets
5
+ from python4cpm.args import Args
6
+ from python4cpm.logger import get_logger
7
+
8
+
9
+ class Python4CPM:
10
+ ACTION_VERIFY = "verifypass"
11
+ ACTION_LOGON = "logon"
12
+ ACTION_CHANGE = "changepass"
13
+ ACTION_PRERECONCILE = "prereconcilepass"
14
+ ACTION_RECONCILE = "reconcilepass"
15
+ _VALID_ACTIONS = (
16
+ ACTION_VERIFY,
17
+ ACTION_LOGON,
18
+ ACTION_CHANGE,
19
+ ACTION_PRERECONCILE,
20
+ ACTION_RECONCILE,
21
+ )
22
+ _SUCCESS_CODE = 0
23
+ _FAILED_RECOVERABLE_CODE = 81
24
+ _FAILED_UNRECOVERABLE_CODE = 89
25
+ _ENV_PREFIX = "PYTHON4CPM_"
26
+
27
+ def __init__(self, name: str) -> None:
28
+ self._name = name
29
+ args = self._get_args()
30
+ self._args = Args(**args)
31
+ self._logger = get_logger(
32
+ self._name,
33
+ self._args.logging,
34
+ self._args.logging_level
35
+ )
36
+ self.log_info("Python4CPM.__init__: initiating...")
37
+ self._log_args()
38
+ self._verify_action()
39
+ secrets = self._get_secrets()
40
+ self._secrets = Secrets(**secrets)
41
+
42
+ @property
43
+ def args(self) -> Args:
44
+ return self._args
45
+
46
+ @property
47
+ def secrets(self) -> Secrets:
48
+ return self._secrets
49
+
50
+ @property
51
+ def logger(self) -> logging.Logger:
52
+ return self._logger
53
+
54
+ def log_debug(self, message: str) -> None:
55
+ if self._logger is not None:
56
+ self._logger.debug(message)
57
+
58
+ def log_info(self, message: str) -> None:
59
+ if self._logger is not None:
60
+ self._logger.info(message)
61
+
62
+ def log_warning(self, message: str) -> None:
63
+ if self._logger is not None:
64
+ self._logger.warning(message)
65
+
66
+ def log_error(self, message: str) -> None:
67
+ if self._logger is not None:
68
+ self._logger.error(message)
69
+
70
+ @classmethod
71
+ def _get_env_key(cls, key: str) -> str:
72
+ return f"{cls._ENV_PREFIX}{key.upper()}"
73
+
74
+ @classmethod
75
+ def _get_args(cls) -> dict:
76
+ args = {}
77
+ for arg in Args.ARGS:
78
+ args[arg] = os.environ.get(cls._get_env_key(arg))
79
+ return args
80
+
81
+ def _get_secrets(self) -> dict:
82
+ secrets = {}
83
+ for secret in Secrets.SECRETS:
84
+ secrets[secret] = os.environ.get(self._get_env_key(secret))
85
+ common_message = f"Python4CPM._get_secrets: {secret} ->"
86
+ if secrets[secret]:
87
+ self.log_info(f"{common_message} [*******]")
88
+ else:
89
+ self.log_info(f"{common_message} [NOT SET]")
90
+ return secrets
91
+
92
+ def _verify_action(self) -> None:
93
+ if self.args.action not in self._VALID_ACTIONS:
94
+ self.log_warning(
95
+ f"Python4CPM._verify_action: unkonwn action -> {self.args.action}"
96
+ )
97
+
98
+ def _log_args(self) -> None:
99
+ for key, value in vars(self._args).items():
100
+ common_message = f"Python4CPM._log_args: {key.strip('_')} ->"
101
+ if value:
102
+ self.log_info(f"{common_message} {value}")
103
+ else:
104
+ self.log_info(f"{common_message} [NOT SET]")
105
+
106
+ def close_fail(self, unrecoverable: bool = False) -> None:
107
+ if unrecoverable is False:
108
+ code = self._FAILED_RECOVERABLE_CODE
109
+ else:
110
+ code = self._FAILED_UNRECOVERABLE_CODE
111
+ self.log_error(f"Python4CPM.close_fail: closing with code {code}")
112
+ sys.exit(code)
113
+
114
+ def close_success(self) -> None:
115
+ self.log_info(
116
+ f"Python4CPM.close_success: closing with code {self._SUCCESS_CODE}"
117
+ )
118
+ sys.exit(self._SUCCESS_CODE)
@@ -0,0 +1,49 @@
1
+ from python4cpm.crypto import Crypto
2
+
3
+
4
+ class Secret:
5
+ def __init__(self, secret: str) -> None:
6
+ self._secret = secret
7
+
8
+ def get(self) -> str:
9
+ if Crypto.ENABLED and self._secret:
10
+ return Crypto.decrypt(self._secret)
11
+ else:
12
+ return self._secret
13
+
14
+
15
+ class Secrets:
16
+ SECRETS = (
17
+ "password",
18
+ "logon_password",
19
+ "reconcile_password",
20
+ "new_password"
21
+ )
22
+
23
+ def __init__(
24
+ self: str,
25
+ password: str,
26
+ logon_password: str,
27
+ reconcile_password: str,
28
+ new_password: str
29
+ ) -> None:
30
+ self._password = Secret(password)
31
+ self._logon_password = Secret(logon_password)
32
+ self._reconcile_password = Secret(reconcile_password)
33
+ self._new_password = Secret(new_password)
34
+
35
+ @property
36
+ def password(self) -> str:
37
+ return self._password
38
+
39
+ @property
40
+ def new_password(self) -> str:
41
+ return self._new_password
42
+
43
+ @property
44
+ def logon_password(self) -> str:
45
+ return self._logon_password
46
+
47
+ @property
48
+ def reconcile_password(self) -> str:
49
+ return self._reconcile_password
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python4cpm
3
- Version: 1.0.14
3
+ Version: 1.0.16
4
4
  Summary: Python for CPM
5
5
  Author-email: Gonzalo Atienza Rela <gonatienza@gmail.com>
6
6
  License: MIT License
@@ -32,9 +32,9 @@ Dynamic: license-file
32
32
 
33
33
  # Python4CPM
34
34
 
35
- A simple way of using python scripts with CyberArk CPM rotations. This module levereages the [Terminal Plugin Controller](https://docs.cyberark.com/pam-self-hosted/latest/en/content/pasimp/plug-in-terminal-plugin-controller.htm) (TPC) in CPM to offload a password rotation logic into a script.
35
+ A simple way of using python scripts with CyberArk CPM rotations. This module levereages 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
36
 
37
- 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 included with the base platform.
37
+ 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
38
 
39
39
  ## Installation
40
40
 
@@ -45,22 +45,23 @@ This platform allows you to duplicate it multiple times, simply changing its set
45
45
  3. Install `python4cpm` in your venv:
46
46
  - If your CPM can connect to the internet, install with `c:\venv\Scripts\pip install python4cpm`.
47
47
  - If your CPM cannot connect to the internet:
48
- - Download the [latest wheel](https://github.com/gonatienza/python4cpm/releases/download/latest/python4cpm-wheel.zip).
48
+ - Download the `python4cpm-wheel.zip` asset from the latest [release](https://github.com/gonatienza/python4cpm/releases).
49
49
  - Copy the file to CPM and extract to a temporary location.
50
50
  - From the temporary location run `c:\venv\Scripts\pip install --no-index --find-links=.\python4cpm-wheel python4cpm`.
51
51
 
52
52
 
53
53
  ### Importing the platform
54
54
 
55
- 1. Download the [latest platform zip file](https://github.com/gonatienza/python4cpm/releases/download/latest/python4cpm-platform.zip).
56
- 2. Import the platform zip file into Privilege Cloud/PVWA `(Administration -> Platform Management -> Import platform)`.
57
- 3. Craft your python script and place it within the bin folder of CPM (`C:\Program Files (x86)\CyberArk\Password Manager\bin`).
58
- 4. Duplicate the imported platform in Privilege Cloud/PVWA `(Administration -> Platform Management -> Application -> Python for CPM)` and name it after your application (e.g., My App).
59
- 5. Edit the duplicated platform and specify the path of your placed script in the bin folder of CPM, under `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonScriptPath -> Value` (e.g., `bin\myapp.py`).
60
- 6. If you used a custom venv location, also update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonExePath -> Value` with the custom path for the venv's `python.exe` file (e.g., `c:\my-venv-path\Scripts\python.exe`).
61
- 7. If you want to disable logging, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLogging -> Value` to `no`.
62
- 8. If you want to change the logging level to `debug`, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLoggingLevel -> Value` to `debug`.
63
- 9. For new applications repeat steps from 3 to 8.
55
+ 1. Download the latest [Credential Management .NET SDK](https://community.cyberark.com/marketplace/s/#a3550000000EkA0AAK-a3950000000jjoOAAQ) and place its content in the bin folder of CPM (`C:\Program Files (x86)\CyberArk\Password Manager\bin`).
56
+ 2. Download the `python4cpm-platform.zip` asset from the [release](https://github.com/gonatienza/python4cpm/releases).
57
+ 3. Import the platform zip file into Privilege Cloud/PVWA `(Administration -> Platform Management -> Import platform)`.
58
+ 4. Craft your python script and place it within the bin folder of CPM (`C:\Program Files (x86)\CyberArk\Password Manager\bin`).
59
+ 5. Duplicate the imported platform in Privilege Cloud/PVWA `(Administration -> Platform Management -> Application -> Python for CPM)` and name it after your application (e.g., My App).
60
+ 6. Edit the duplicated platform and specify the path of your placed script in the bin folder of CPM, under `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonScriptPath -> Value` (e.g., `bin\myapp.py`).
61
+ 7. If you used a custom venv location, also update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonExePath -> Value` with the custom path for the venv's `python.exe` file (e.g., `c:\my-venv-path\Scripts\python.exe`).
62
+ 8. If you want to disable logging, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLogging -> Value` to `no`.
63
+ 9. If you want to change the logging level to `debug`, update `Target Account Platform -> Automatic Platform Management -> Additional Policy Settings -> Parameters -> PythonLoggingLevel -> Value` to `debug`.
64
+ 10. For new applications repeat steps from 4 to 9.
64
65
 
65
66
 
66
67
  ## Python Script
@@ -137,7 +138,7 @@ def change(from_reconcile=False):
137
138
 
138
139
  if __name__ == "__main__":
139
140
  try:
140
- if action == Python4CPM.ACTION_VERIFY: # class attribute ACTION_VERIFY holds the verify action value
141
+ if p4cpm.args.action == Python4CPM.ACTION_VERIFY: # class attribute ACTION_VERIFY holds the verify action value
141
142
  verify()
142
143
  p4cpm.close_success() # terminate with success state
143
144
  elif p4cpm.args.action == Python4CPM.ACTION_LOGON: # class attribute ACTION_LOGON holds the logon action value
@@ -159,7 +160,7 @@ if __name__ == "__main__":
159
160
  ## p4cpm.log_error("reconciliation is not supported") # let the logs know that reconciliation is not supported
160
161
  ## p4cpm.close_fail() # let CPM know to check the logs
161
162
  else:
162
- p4cpm.log_error(f"invalid action: '{action}'") # logs into Logs/ThirdParty/Python4CPM/MyApp.log
163
+ p4cpm.log_error(f"invalid action: '{p4cpm.args.action}'") # logs into Logs/ThirdParty/Python4CPM/MyApp.log
163
164
  p4cpm.close_fail(unrecoverable=True) # terminate with unrecoverable failed state
164
165
  except Exception as e:
165
166
  p4cpm.log_error(f"{type(e).__name__}: {e}")
@@ -189,14 +190,15 @@ As with any python venv, you can install dependancies in your venv.
189
190
 
190
191
  ## Dev Helper:
191
192
 
192
- TPC is a binary Terminal Plugin Controller in CPM. It passes information to Python4CPM through arguments and prompts when calling the script.
193
- For dev purposes, `TPCHelper` is a companion helper that simplifies the instantiation of the `Python4CPM` object by simulating how TPC passes those arguments and prompts.
193
+ For dev purposes, `NETHelper` is a companion helper that simplifies the instantiation of the `Python4CPM` object by simulating how the plugin passes arguments and secrets to `Python4CPM`.
194
194
  Install this module (in a dev workstation) with:
195
195
 
196
196
  ```bash
197
197
  pip install python4cpm
198
198
  ```
199
199
 
200
+ **Note**: As CPM runs in Windows, the plugin was built to pass secrets securely to the `Python4CPM.crypto` module using the Data Protection API (DPAPI). For dev purposes in Linux/Mac dev workstations, those secrets will appear as plaintext in the environment of the process. This is informational only, the module will use its encryption/decryption capabilities automatically in Windows and you do not have to do anything specific to enable it.
201
+
200
202
  ### Example:
201
203
 
202
204
  ```python
@@ -2,8 +2,12 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/python4cpm/__init__.py
5
+ src/python4cpm/args.py
6
+ src/python4cpm/crypto.py
7
+ src/python4cpm/logger.py
8
+ src/python4cpm/nethelper.py
5
9
  src/python4cpm/python4cpm.py
6
- src/python4cpm/tpchelper.py
10
+ src/python4cpm/secrets.py
7
11
  src/python4cpm.egg-info/PKG-INFO
8
12
  src/python4cpm.egg-info/SOURCES.txt
9
13
  src/python4cpm.egg-info/dependency_links.txt
@@ -1,13 +0,0 @@
1
- from python4cpm.python4cpm import Args, Secret, Secrets, Python4CPM
2
- from python4cpm.tpchelper import TPCHelper
3
- from importlib.metadata import version as __version
4
-
5
- __version__ = __version(__name__)
6
-
7
- __all__ = [
8
- Args,
9
- Secret,
10
- Secrets,
11
- Python4CPM,
12
- TPCHelper
13
- ]
@@ -1,257 +0,0 @@
1
- import os
2
- import sys
3
- import logging
4
- from logging.handlers import RotatingFileHandler
5
- from argparse import ArgumentParser
6
-
7
-
8
- class Args:
9
- ARGS = (
10
- "action",
11
- "address",
12
- "username",
13
- "logon_username",
14
- "reconcile_username",
15
- "logging",
16
- "logging_level"
17
- )
18
-
19
- def __init__(
20
- self: str,
21
- action: str,
22
- address: str,
23
- username: str,
24
- reconcile_username: str,
25
- logon_username: str,
26
- logging: str,
27
- logging_level: str
28
- ) -> None:
29
- self._action = action
30
- self._address = address
31
- self._username = username
32
- self._reconcile_username = reconcile_username
33
- self._logon_username = logon_username
34
- self._logging = logging
35
- self._logging_level = logging_level
36
-
37
- @property
38
- def action(self) -> str:
39
- return self._action
40
-
41
- @property
42
- def address(self) -> str:
43
- return self._address
44
-
45
- @property
46
- def username(self) -> str:
47
- return self._username
48
-
49
- @property
50
- def reconcile_username(self) -> str:
51
- return self._reconcile_username
52
-
53
- @property
54
- def logon_username(self) -> str:
55
- return self._logon_username
56
-
57
- @property
58
- def logging(self) -> str:
59
- return self._logging
60
-
61
- @property
62
- def logging_level(self) -> str:
63
- return self._logging_level
64
-
65
-
66
- class Secret:
67
- def __init__(self, value: str) -> None:
68
- self._secret = value
69
-
70
- def __repr__(self) -> str:
71
- return f"{self.__class__.__name__}('***')"
72
-
73
- def get(self) -> str:
74
- return self._secret
75
-
76
-
77
- class Secrets:
78
- SECRETS = (
79
- "password",
80
- "logon_password",
81
- "reconcile_password",
82
- "new_password"
83
- )
84
-
85
- def __init__(
86
- self: str,
87
- password: str,
88
- logon_password: str,
89
- reconcile_password: str,
90
- new_password: str
91
- ) -> None:
92
- self._password = Secret(password)
93
- self._logon_password = Secret(logon_password)
94
- self._reconcile_password = Secret(reconcile_password)
95
- self._new_password = Secret(new_password)
96
-
97
- @property
98
- def password(self) -> str:
99
- return self._password
100
-
101
- @property
102
- def new_password(self) -> str:
103
- return self._new_password
104
-
105
- @property
106
- def logon_password(self) -> str:
107
- return self._logon_password
108
-
109
- @property
110
- def reconcile_password(self) -> str:
111
- return self._reconcile_password
112
-
113
-
114
- class Python4CPM:
115
- ACTION_VERIFY = "verifypass"
116
- ACTION_LOGON = "logon"
117
- ACTION_CHANGE = "changepass"
118
- ACTION_PRERECONCILE = "prereconcilepass"
119
- ACTION_RECONCILE = "reconcilepass"
120
- _VALID_ACTIONS = (
121
- ACTION_VERIFY,
122
- ACTION_LOGON,
123
- ACTION_CHANGE,
124
- ACTION_PRERECONCILE,
125
- ACTION_RECONCILE,
126
- )
127
- _LOGS_DIR = os.path.join("Logs", "ThirdParty", "Python4CPM")
128
- _CPM_ROOT_DIR = "C:\\Program Files (x86)\\CyberArk\\Password Manager"
129
- if os.path.exists(_CPM_ROOT_DIR):
130
- _LOGS_DIR = os.path.join(_CPM_ROOT_DIR, _LOGS_DIR)
131
- _LOGGING_ENABLED_VALUE = "yes"
132
- _LOGGING_LEVELS = {
133
- "info": logging.INFO,
134
- "debug": logging.DEBUG
135
- }
136
- _SECRETS_PADDING = "@@@"
137
- _SUCCESS_PROMPT = "~~~SUCCESS~~~"
138
- _FAILED_RECOVERABLE_PROMPT = "~~~FAILED_RECOVERABLE~~~"
139
- _FAILED_UNRECOVERABLE_PROMPT = "~~~FAILED_UNRECOVERABLE~~~"
140
-
141
- def __init__(self, name: str) -> None:
142
- self._name = name
143
- args = self._get_args()
144
- self._args = Args(**args)
145
- self._logger = self._get_logger(self._name)
146
- self.log_info("Python4CPM.__init__: initiating...")
147
- self._log_args()
148
- self._verify_action()
149
- secrets = self._get_secrets()
150
- self._secrets = Secrets(**secrets)
151
-
152
- @property
153
- def args(self) -> Args:
154
- return self._args
155
-
156
- @property
157
- def secrets(self) -> Secrets:
158
- return self._secrets
159
-
160
- @property
161
- def logger(self) -> logging.Logger:
162
- return self._logger
163
-
164
- def log_debug(self, message: str) -> None:
165
- if self._logger is not None:
166
- self._logger.debug(message)
167
-
168
- def log_info(self, message: str) -> None:
169
- if self._logger is not None:
170
- self._logger.info(message)
171
-
172
- def log_warning(self, message: str) -> None:
173
- if self._logger is not None:
174
- self._logger.warning(message)
175
-
176
- def log_error(self, message: str) -> None:
177
- if self._logger is not None:
178
- self._logger.error(message)
179
-
180
- @staticmethod
181
- def _get_args() -> dict:
182
- parser = ArgumentParser()
183
- for arg in Args.ARGS:
184
- parser.add_argument(f"--{arg}")
185
- args = parser.parse_args()
186
- return dict(vars(args))
187
-
188
- def _get_secrets(self) -> dict:
189
- secrets = {}
190
- try:
191
- for secret in Secrets.SECRETS:
192
- prompt = self._SECRETS_PADDING + secret + self._SECRETS_PADDING
193
- secrets[secret] = input(prompt)
194
- common_message = f"Python4CPM._get_secrets: {secret} ->"
195
- if secrets[secret]:
196
- self.log_info(f"{common_message} [*******]")
197
- else:
198
- self.log_info(f"{common_message} [NOT SET]")
199
- except Exception as e:
200
- self.log_error(f"Python4CPM._get_secrets: {type(e).__name__}: {e}")
201
- self.close_fail()
202
- return secrets
203
-
204
- def _verify_action(self) -> None:
205
- if self.args.action not in self._VALID_ACTIONS:
206
- self.log_warning(
207
- f"Python4CPM._verify_action: unkonwn action -> {self.args.action}"
208
- )
209
-
210
- def _log_args(self) -> None:
211
- for key, value in vars(self._args).items():
212
- common_message = f"Python4CPM._log_args: {key.strip('_')} ->"
213
- if value:
214
- self.log_info(f"{common_message} {value}")
215
- else:
216
- self.log_info(f"{common_message} [NOT SET]")
217
-
218
- def _get_logger(self, name: str) -> logging.Logger:
219
- if self._args.logging is None:
220
- return None
221
- if self._args.logging.lower() != self._LOGGING_ENABLED_VALUE:
222
- return None
223
- os.makedirs(self._LOGS_DIR, exist_ok=True)
224
- logs_file = os.path.join(self._LOGS_DIR, f"{name}.log")
225
- logger = logging.getLogger(name)
226
- logging_level = self._args.logging_level.lower()
227
- if logging_level in self._LOGGING_LEVELS:
228
- logger.setLevel(self._LOGGING_LEVELS[logging_level])
229
- else:
230
- logger.setLevel(self._LOGGING_LEVELS["info"])
231
- handler = RotatingFileHandler(
232
- filename=logs_file,
233
- maxBytes=1 * 1024 * 1024,
234
- backupCount=2
235
- )
236
- formatter = logging.Formatter(
237
- fmt="%(asctime)s | %(levelname)s | %(message)s",
238
- datefmt="%Y-%m-%d %H:%M:%S"
239
- )
240
- handler.setFormatter(formatter)
241
- logger.addHandler(handler)
242
- return logger
243
-
244
- def close_fail(self, unrecoverable: bool = False) -> None:
245
- if unrecoverable is False:
246
- prompt = self._FAILED_RECOVERABLE_PROMPT
247
- else:
248
- prompt = self._FAILED_UNRECOVERABLE_PROMPT
249
- self.log_error(f"Python4CPM.close_fail: closing with {prompt}")
250
- print(prompt)
251
- sys.exit(1)
252
-
253
- def close_success(self) -> None:
254
- prompt = self._SUCCESS_PROMPT
255
- self.log_info(f"Python4CPM.close_success: closing with {prompt}")
256
- print(prompt)
257
- sys.exit(0)
@@ -1,44 +0,0 @@
1
- from .python4cpm import Python4CPM, Args
2
- from unittest import mock
3
-
4
-
5
- class TPCHelper:
6
- @classmethod
7
- def run(
8
- cls,
9
- action: str = "",
10
- address: str = "",
11
- username: str = "",
12
- logon_username: str = "",
13
- reconcile_username: str = "",
14
- logging: str = "",
15
- logging_level: str = "",
16
- password: str = "",
17
- logon_password: str = "",
18
- reconcile_password: str = "",
19
- new_password: str = ""
20
- ) -> Python4CPM:
21
- args = [
22
- "", # sys.argv[0] is ignored by argparse
23
- f"--{Args.ARGS[0]}={action}",
24
- f"--{Args.ARGS[1]}={address}",
25
- f"--{Args.ARGS[2]}={username}",
26
- f"--{Args.ARGS[3]}={logon_username}",
27
- f"--{Args.ARGS[4]}={reconcile_username}",
28
- f"--{Args.ARGS[5]}={logging}",
29
- f"--{Args.ARGS[6]}={logging_level}"
30
- ]
31
- secrets = (
32
- password,
33
- logon_password,
34
- reconcile_password,
35
- new_password
36
- )
37
- with mock.patch(
38
- "sys.argv",
39
- args
40
- ), mock.patch(
41
- "builtins.input",
42
- side_effect=secrets
43
- ):
44
- return Python4CPM(cls.__name__)
File without changes
File without changes