From 447af61703fc2e93fbc2e39cbdccaf6feb3c591d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Rom=C3=A1n?= Date: Wed, 10 Jun 2026 13:58:40 -0700 Subject: [PATCH] feat(system): add secrets module remove runUpdateQuery from db module --- .../ignition/common/script/abc/__init__.py | 15 +++ .../ignition/common/secrets/__init__.py | 66 ++++++++++ .../ignition/gateway/secrets/__init__.py | 37 ++++++ src/system/db.py | 47 -------- src/system/secrets.py | 113 ++++++++++++++++++ .../ignition/common/script/abc/__init__.pyi | 5 + .../ignition/common/secrets/__init__.pyi | 28 +++++ .../ignition/gateway/secrets/__init__.pyi | 18 +++ stubs/stubs/system/db.pyi | 7 -- stubs/stubs/system/secrets.pyi | 15 +++ 10 files changed, 297 insertions(+), 54 deletions(-) create mode 100644 src/com/inductiveautomation/ignition/common/secrets/__init__.py create mode 100644 src/com/inductiveautomation/ignition/gateway/secrets/__init__.py create mode 100644 src/system/secrets.py create mode 100644 stubs/stubs/com/inductiveautomation/ignition/common/secrets/__init__.pyi create mode 100644 stubs/stubs/com/inductiveautomation/ignition/gateway/secrets/__init__.pyi create mode 100644 stubs/stubs/system/secrets.pyi diff --git a/src/com/inductiveautomation/ignition/common/script/abc/__init__.py b/src/com/inductiveautomation/ignition/common/script/abc/__init__.py index 456e927..472a013 100644 --- a/src/com/inductiveautomation/ignition/common/script/abc/__init__.py +++ b/src/com/inductiveautomation/ignition/common/script/abc/__init__.py @@ -5,6 +5,7 @@ "AbstractJythonSequence", "AbstractMutableJythonMap", "AbstractMutableJythonSequence", + "ContextManager", "JythonMap", "JythonSequence", "MutableJythonMap", @@ -280,3 +281,17 @@ def remove(self, element): def sort(self, *args, **kwargs): # type: (*Any, **Union[str, unicode]) -> None pass + + +class ContextManager(PyObject): + def __init__(self): + # type: () -> None + super(ContextManager, self).__init__() + + def __enter__(self): + # type: () -> None + print("Enter") + + def __exit__(self, exc_type, exc_val, exc_tb): + # type: (object, object, object) -> None + print("Exit") diff --git a/src/com/inductiveautomation/ignition/common/secrets/__init__.py b/src/com/inductiveautomation/ignition/common/secrets/__init__.py new file mode 100644 index 0000000..7ae5297 --- /dev/null +++ b/src/com/inductiveautomation/ignition/common/secrets/__init__.py @@ -0,0 +1,66 @@ +from __future__ import print_function + +from typing import Union + +from java.lang import Record + +from com.inductiveautomation.ignition.common.script.abc import ContextManager +from com.inductiveautomation.ignition.gateway.secrets import Plaintext + + +class PyPlaintext(ContextManager): + def __init__(self, plaintext): + # type: (Plaintext) -> None + super(PyPlaintext, self).__init__() + + def clear(self): + # type: () -> None + pass + + def getSecretsAsBytes(self): + # type: () -> bytearray + pass + + def getSecretsAsString(self, charsetName=None): + # type: (Union[str, unicode, None]) -> Union[str, unicode] + pass + + +class SecretMeta(Record): + def __init__( + self, + name, # type: Union[str, unicode] + ): + # type: (...) -> None + super(SecretMeta, self).__init__() + self._name = name + + def name(self): + # type: () -> Union[str, unicode] + return self._name + + +class SecretProviderMeta(Record): + def __init__( + self, + name, # type: Union[str, unicode] + description, # type: Union[str, unicode] + type_, # type: Union[str, unicode] + ): + # type: (...) -> None + super(SecretProviderMeta, self).__init__() + self._name = name + self._description = description + self._type = type_ + + def description(self): + # type: () -> Union[str, unicode] + return self._description + + def name(self): + # type: () -> Union[str, unicode] + return self._name + + def type(self): + # type: () -> Union[str, unicode] + return self._type diff --git a/src/com/inductiveautomation/ignition/gateway/secrets/__init__.py b/src/com/inductiveautomation/ignition/gateway/secrets/__init__.py new file mode 100644 index 0000000..2ede948 --- /dev/null +++ b/src/com/inductiveautomation/ignition/gateway/secrets/__init__.py @@ -0,0 +1,37 @@ +from typing import Optional, Union + +from java.io import Closeable +from java.lang import Object +from java.nio.charset import Charset + + +class Plaintext(Object, Closeable): + def __init__(self): + # type: () -> None + super(Plaintext, self).__init__() + + def clear(self): + # type: () -> None + pass + + def close(self): + # type: () -> None + pass + + @staticmethod + def fromBytes(bytes): + # type: (bytearray) -> Plaintext + return Plaintext() + + @staticmethod + def fromString(str, charset=None): + # type: (Union[str, unicode], Optional[Charset]) -> Plaintext + return Plaintext() + + def getAsString(self): + # type: () -> Union[str, unicode] + pass + + def getBytes(self): + # type: () -> bytearray + pass diff --git a/src/system/db.py b/src/system/db.py index 2d12e8a..dc4b6e8 100644 --- a/src/system/db.py +++ b/src/system/db.py @@ -68,7 +68,6 @@ "runPrepUpdate", "runSFPrepUpdate", "runScalarPrepQuery", - "runUpdateQuery", "setDatasourceConnectURL", "setDatasourceEnabled", "setDatasourceMaxConnections", @@ -714,52 +713,6 @@ def runScalarPrepQuery( return 42 -def runUpdateQuery( - query, # type: Union[str, unicode] - database="", # type: Union[str, unicode] - tx=None, # type: Union[str, unicode, None] - getKey=False, # type: bool - skipAudit=True, # type: bool -): - # type: (...) -> int - """Runs a query against a database connection, returning the number - of rows affected. - - Typically this is an UPDATE, INSERT, or DELETE query. If no database - is specified, or the database is the empty-string "", then the - current project's default database connection will be used. - - Note: - You may want to use the runPrepUpdate query if your query is - constructed with user input (to avoid the user's input from - breaking your syntax) or if you need to insert binary or BLOB - data. - - Args: - query: A SQL query, usually an INSERT, UPDATE, or DELETE query, - to run. - database: The name of the database connection to execute - against. If omitted or "", the project's default database - connection will be used. - tx: A transaction identifier. If omitted, the update will be - executed in its own transaction. - getKey: A flag indicating whether or not the result should be - the number of rows returned (getKey=0) or the newly - generated key value that was created as a result of the - update (getKey=1). Not all databases support automatic - retrieval of generated keys. - skipAudit: A flag which, if set to True, will cause the update - query to skip the audit system. Useful for some queries that - have fields which won't fit into the audit log. - - Returns: - The number of rows affected by the query, or the key value that - was generated, depending on the value of the getKey flag. - """ - print(query, database, tx, getKey, skipAudit) - return 1 - - def setDatasourceConnectURL(name, connectUrl): # type: (Union[str, unicode], Union[str, unicode]) -> None """Changes the connect URL for a given database connection. diff --git a/src/system/secrets.py b/src/system/secrets.py new file mode 100644 index 0000000..1b9c8da --- /dev/null +++ b/src/system/secrets.py @@ -0,0 +1,113 @@ +"""Secrets Functions. + +The following functions are used to interact with encrypted secrets for +Secret Providers on the Gateway. +""" + +from __future__ import print_function + +__all__ = [ + "decrypt", + "encrypt", + "getProviders", + "getSecrets", + "readSecretValue", +] + +from typing import Any, Dict, List, Union + +from com.inductiveautomation.ignition.common.secrets import ( + PyPlaintext, + SecretMeta, + SecretProviderMeta, +) +from com.inductiveautomation.ignition.gateway.secrets import Plaintext + + +def decrypt(json): + # type: (Any) -> PyPlaintext + """Decrypts the given JSON object containing an encrypted secret + using the system encryption service. + + Args: + json: The JSON object containing the encrypted secret to + decrypt. + + Returns: + The decrypted value of the JSON string. + """ + return PyPlaintext(Plaintext.fromString(json)) + + +def encrypt(*args): + # type: (*Any) -> Dict[Union[str, unicode], Any] + """Encrypts supplied data using the Secrets Management system + encryption service and returns a JSON object containing the + encrypted secret. + + Args: + *args: Variable length argument list. + + Returns: + A PyDictionary containing the encrypted secret or None if the + JSON is empty. + """ + return { + "ciphertext": None, + "encrypted_key": None, + "iv": None, + "protected": True, + "tag": None, + } + + +def getProviders(): + # type: () -> List[SecretProviderMeta] + """Returns a list of Secret Providers configured in the Secrets + Management system on the Gateway. Each list entry includes the name, + description, and type of the provider. + + Returns: + A List of SecretProviderMeta instances that represent all of + the Secret Providers. + """ + return [ + SecretProviderMeta( + "SecretProviderName", "SecretProviderDescription", "SecretProviderType" + ) + ] + + +def getSecrets(providerName): + # type: (Union[str, unicode]) -> List[SecretMeta] + """Returns a list of objects representing all secrets available for + the named Secret Provider. + + Each list entry includes the name of the secret. + + Args: + providerName: The name of the Secret Provider to fetch secrets + from. + + Returns: + A list of SecretMeta instances that represent all secret names. + """ + print(providerName) + return [SecretMeta("SecretName")] + + +def readSecretValue(providerName, secretName): + # type: (Union[str, unicode], Union[str, unicode]) -> PyPlaintext + """Reads the plaintext value of a secret given the name of the + Secret Provider and the name of the secret. + + Args: + providerName: The name of the Secret Provider to read the secret + from. + secretName: The name of the secret to read. + + Returns: + A PyPlaintext instance that contains the secret. + """ + print(providerName, secretName) + return PyPlaintext(Plaintext()) diff --git a/stubs/stubs/com/inductiveautomation/ignition/common/script/abc/__init__.pyi b/stubs/stubs/com/inductiveautomation/ignition/common/script/abc/__init__.pyi index ec4801b..deac761 100644 --- a/stubs/stubs/com/inductiveautomation/ignition/common/script/abc/__init__.pyi +++ b/stubs/stubs/com/inductiveautomation/ignition/common/script/abc/__init__.pyi @@ -86,3 +86,8 @@ class AbstractMutableJythonSequence(AbstractJythonSequence, MutableJythonSequenc def pop(self, index: Optional[int] = ...) -> PyObject: ... def remove(self, element: PyObject) -> None: ... def sort(self, *args: Any, **kwargs: Union[str, unicode]) -> None: ... + +class ContextManager(PyObject): + def __init__(self) -> None: ... + def __enter__(self) -> None: ... + def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: ... diff --git a/stubs/stubs/com/inductiveautomation/ignition/common/secrets/__init__.pyi b/stubs/stubs/com/inductiveautomation/ignition/common/secrets/__init__.pyi new file mode 100644 index 0000000..b27d554 --- /dev/null +++ b/stubs/stubs/com/inductiveautomation/ignition/common/secrets/__init__.pyi @@ -0,0 +1,28 @@ +from typing import Union + +from com.inductiveautomation.ignition.common.script.abc import ContextManager +from com.inductiveautomation.ignition.gateway.secrets import Plaintext +from java.lang import Record + +class PyPlaintext(ContextManager): + def __init__(self, plaintext: Plaintext) -> None: ... + def clear(self) -> None: ... + def getSecretsAsBytes(self) -> bytearray: ... + def getSecretsAsString( + self, charsetName: Union[str, unicode, None] = ... + ) -> Union[str, unicode]: ... + +class SecretMeta(Record): + def __init__(self, name: Union[str, unicode]) -> None: ... + def name(self) -> Union[str, unicode]: ... + +class SecretProviderMeta(Record): + def __init__( + self, + name: Union[str, unicode], + description: Union[str, unicode], + type_: Union[str, unicode], + ) -> None: ... + def description(self) -> Union[str, unicode]: ... + def name(self) -> Union[str, unicode]: ... + def type(self) -> Union[str, unicode]: ... diff --git a/stubs/stubs/com/inductiveautomation/ignition/gateway/secrets/__init__.pyi b/stubs/stubs/com/inductiveautomation/ignition/gateway/secrets/__init__.pyi new file mode 100644 index 0000000..7392677 --- /dev/null +++ b/stubs/stubs/com/inductiveautomation/ignition/gateway/secrets/__init__.pyi @@ -0,0 +1,18 @@ +from typing import Optional, Union + +from java.io import Closeable +from java.lang import Object +from java.nio.charset import Charset as Charset + +class Plaintext(Object, Closeable): + def __init__(self) -> None: ... + def clear(self) -> None: ... + def close(self) -> None: ... + @staticmethod + def fromBytes(bytes: bytearray) -> Plaintext: ... + @staticmethod + def fromString( + str: Union[str, unicode], charset: Optional[Charset] = ... + ) -> Plaintext: ... + def getAsString(self) -> Union[str, unicode]: ... + def getBytes(self) -> bytearray: ... diff --git a/stubs/stubs/system/db.pyi b/stubs/stubs/system/db.pyi index 04bc554..3626323 100644 --- a/stubs/stubs/system/db.pyi +++ b/stubs/stubs/system/db.pyi @@ -128,13 +128,6 @@ def runScalarPrepQuery( database: Union[str, unicode] = ..., tx: Union[str, unicode, None] = ..., ) -> Any: ... -def runUpdateQuery( - query: Union[str, unicode], - database: Union[str, unicode] = ..., - tx: Union[str, unicode, None] = ..., - getKey: bool = ..., - skipAudit: bool = ..., -) -> int: ... def setDatasourceConnectURL( name: Union[str, unicode], connectUrl: Union[str, unicode] ) -> None: ... diff --git a/stubs/stubs/system/secrets.pyi b/stubs/stubs/system/secrets.pyi new file mode 100644 index 0000000..a947214 --- /dev/null +++ b/stubs/stubs/system/secrets.pyi @@ -0,0 +1,15 @@ +from typing import Any, Dict, List, Union + +from com.inductiveautomation.ignition.common.secrets import ( + PyPlaintext, + SecretMeta, + SecretProviderMeta, +) + +def decrypt(json: Any) -> PyPlaintext: ... +def encrypt(*args: Any) -> Dict[Union[str, unicode], Any]: ... +def getProviders() -> List[SecretProviderMeta]: ... +def getSecrets(providerName: Union[str, unicode]) -> List[SecretMeta]: ... +def readSecretValue( + providerName: Union[str, unicode], secretName: Union[str, unicode] +) -> PyPlaintext: ...