Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/com/inductiveautomation/ignition/common/script/abc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"AbstractJythonSequence",
"AbstractMutableJythonMap",
"AbstractMutableJythonSequence",
"ContextManager",
"JythonMap",
"JythonSequence",
"MutableJythonMap",
Expand Down Expand Up @@ -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):
Comment on lines +286 to +295

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): ContextManager’s enter/exit behavior is surprising for a reusable base class.

__enter__ currently returns None and both methods print on entry/exit. For a reusable base class, with ContextManager() as cm: (or via subclasses like PyPlaintext) will bind cm to None, which is likely to cause subtle bugs, and the prints will create noisy logs. Prefer returning self from __enter__ and removing these prints, or make this an abstract shell that subclasses override.

# type: (object, object, object) -> None
print("Exit")
66 changes: 66 additions & 0 deletions src/com/inductiveautomation/ignition/common/secrets/__init__.py
Original file line number Diff line number Diff line change
@@ -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
37 changes: 37 additions & 0 deletions src/com/inductiveautomation/ignition/gateway/secrets/__init__.py
Original file line number Diff line number Diff line change
@@ -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
47 changes: 0 additions & 47 deletions src/system/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
"runPrepUpdate",
"runSFPrepUpdate",
"runScalarPrepQuery",
"runUpdateQuery",
"setDatasourceConnectURL",
"setDatasourceEnabled",
"setDatasourceMaxConnections",
Expand Down Expand Up @@ -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.
Expand Down
113 changes: 113 additions & 0 deletions src/system/secrets.py
Original file line number Diff line number Diff line change
@@ -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.
Comment on lines +81 to +90

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Printing providerName in getSecrets introduces unexpected side effects.

Having getSecrets print providerName forces all callers to get unexpected console output, which is unusual for an accessor and may break scripts that rely on clean stdout. If this is only for debugging, remove the print or replace it with a proper, configurable logging mechanism.

Suggested implementation:

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.
    """
    return [SecretMeta("SecretName")]
__all__ = [
    "decrypt",
    "encrypt",
    "getProviders",
    "getSecrets",


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())
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...
Original file line number Diff line number Diff line change
@@ -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]: ...
Original file line number Diff line number Diff line change
@@ -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: ...
7 changes: 0 additions & 7 deletions stubs/stubs/system/db.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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: ...
Expand Down
15 changes: 15 additions & 0 deletions stubs/stubs/system/secrets.pyi
Original file line number Diff line number Diff line change
@@ -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: ...