Update Security Level Configuration via Script

I am wondering how I can access updateSecurityLevelsConfig through the SecurityLevelManager interface to programmatically import a new Security Levels tree within a Gateway Script.

Anytime I attempt to import the SecurityLevelConfig, the logger setup I have returns the following: cannot import name SecurityLevelConfig.

Any suggestions?

Here's an example script that attempts to compose a new node tree and add in a new node under the Roles node:

def handleMessage(payload):
	logger = system.util.getLogger('GatewayScriptManager')
	try:
	    from com.inductiveautomation.ignition.gateway import IgnitionGateway
	    from com.inductiveautomation.ignition.gateway.auth.security.level import SecurityLevelManager
	    from com.inductiveautomation.ignition.gateway.auth.security.level import SecurityLevelConfig
	    from com.google.common.collect import ImmutableList
	    
	    def cloneAndModify(children):
	        newChildren = []
	        for child in children:
	            modifiedChild = SecurityLevelConfig(
	                child.getName(),
	                child.getDescription(),
	                cloneAndModify(child.getChildren())
	            )
	            newChildren.append(modifiedChild)
	        return newChildren
	    
	    def addTestRole(rootConfigs):
	        updated = []
	        for cfg in rootConfigs:
	            if cfg.getName() == "Authenticated":
	                rolesNode = None
	                rolesIndex = None
	                
	                # Find "Roles"
	                for i, c in enumerate(cfg.getChildren()):
	                    if c.getName() == "Roles":
	                        rolesNode = c
	                        rolesIndex = i
	                        break
	                
	                if rolesNode is None:
	                    raise Exception("Roles node not found in Authenticated hierarchy.")
	                
	                # Build Test Role node
	                testRole = SecurityLevelConfig(
	                    "Test Role",
	                    None,
	                    [
	                        SecurityLevelConfig(
	                            "SecurityZones",
	                            "Represents the security zones that a user has.",
	                            [SecurityLevelConfig("Default", None, [])]
	                        )
	                    ]
	                )
	                
	                # Rebuild Roles children
	                updatedRolesChildren = cloneAndModify(rolesNode.getChildren())
	                updatedRolesChildren.append(testRole)
	    
	                updatedRolesNode = SecurityLevelConfig(
	                    "Roles",
	                    rolesNode.getDescription(),
	                    updatedRolesChildren
	                )
	                
	                # Rebuild Authenticated node
	                newAuthenticatedChildren = cloneAndModify(cfg.getChildren())
	                newAuthenticatedChildren[rolesIndex] = updatedRolesNode
	                
	                updatedCfg = SecurityLevelConfig(
	                    cfg.getName(),
	                    cfg.getDescription(),
	                    newAuthenticatedChildren
	                )
	            else:
	                updatedCfg = SecurityLevelConfig(
	                    cfg.getName(),
	                    cfg.getDescription(),
	                    cloneAndModify(cfg.getChildren())
	                )
	            
	            updated.append(updatedCfg)
	        
	        return updated
	    
	    # Execute update
	    gw_context = IgnitionGateway.get()
	    sec_mgr = gw_context.getSecurityLevelManager()
	    
	    curr_lvls = sec_mgr.getSecurityLevelsConfig()
	    new_lvls = addTestRole(curr_lvls)
	    
	    sec_mgr.updateSecurityLevelsConfig(ImmutableList.copyOf(new_lvls))
	    
	except Exception as e:
	    logger.info(str(e))

First question, is this code LLM generated?

Next, don't define functions in a message handler or other event scripts. Define all your functions in a project library script, including the message handler logic. Then call your function from the actual message handler event.

Additionally, don't import anything inside of a function, import it at the top of the project library script, outside of any function definition. E.G:

from com.inductiveautomation.ignition.gateway import IgnitionGateway
from com.inductiveautomation.ignition.gateway.auth.security.level import SecurityLevelManager
from com.inductiveautomation.ignition.gateway.auth.security.level import SecurityLevelConfig
from com.google.common.collect import ImmutableList

def myMessageHandler(payload):
	""" """
	# blah blah blah

You might be running into a known issue with scoping/library availability in the event scripts, defining your functions in the project library script resolves this.

I would also recommend adding

except java.lang.Throwable as t:
    logger.warn("Java exception occurred", t)

to your try block to catch any java errors. Your current try block will only catch python exceptions, which do not include Java errors.

1 Like

Haven't you heard of the AI bubble? Of course!

It's honestly much more efficient to prompt "I want to do x" and have an LLM generate a code block that already knows the interfaces defined in the documentation that I didn't even know existed prior to looking through the Sources breakdown.

Per the guidance, I reformatted the original logic to be a script within the project library and am now calling the script from the Gateway Message Handler via ScriptName.main():

from com.inductiveautomation.ignition.gateway import IgnitionGateway
from com.inductiveautomation.ignition.gateway.auth.security.level import SecurityLevelManager
from com.inductiveautomation.ignition.gateway.auth.security.level import SecurityLevelConfig
from com.google.common.collect import ImmutableList

logger = system.util.getLogger('UserManagement')

def cloneAndModify(children):
    try:
        newChildren = []
        for child in children:
            modifiedChild = SecurityLevelConfig(
                child.getName(),
                child.getDescription(),
                cloneAndModify(child.getChildren())
            )
            newChildren.append(modifiedChild)
        return newChildren
    except Exception as e:
        logger.info(str(e))
		
def addTestRole(rootConfigs):
    try:
        updated = []
        for cfg in rootConfigs:
            if cfg.getName() == "Authenticated":
                rolesNode = None
                rolesIndex = None
                
                # Find "Roles"
                for i, c in enumerate(cfg.getChildren()):
                    if c.getName() == "Roles":
                        rolesNode = c
                        rolesIndex = i
                        break
                
                if rolesNode is None:
                    raise Exception("Roles node not found in Authenticated hierarchy.")
                
                # Build Test Role node
                testRole = SecurityLevelConfig(
                    "Test Role",
                    None,
                    [
                        SecurityLevelConfig(
                            "SecurityZones",
                            "Represents the security zones that a user has.",
                            [SecurityLevelConfig("Default", None, [])]
                        )
                    ]
                )
                
                # Rebuild Roles children
                updatedRolesChildren = cloneAndModify(rolesNode.getChildren())
                updatedRolesChildren.append(testRole)

                updatedRolesNode = SecurityLevelConfig(
                    "Roles",
                    rolesNode.getDescription(),
                    updatedRolesChildren
                )
                
                # Rebuild Authenticated node
                newAuthenticatedChildren = cloneAndModify(cfg.getChildren())
                newAuthenticatedChildren[rolesIndex] = updatedRolesNode
                
                updatedCfg = SecurityLevelConfig(
                    cfg.getName(),
                    cfg.getDescription(),
                    newAuthenticatedChildren
                )
            else:
                updatedCfg = SecurityLevelConfig(
                    cfg.getName(),
                    cfg.getDescription(),
                    cloneAndModify(cfg.getChildren())
                )
            
            updated.append(updatedCfg)
        
        return updated
    except Exception as e:
        logger.info(str(e))
        
def main():
    try:
        # Execute update
        gw_context = IgnitionGateway.get()
        sec_mgr = gw_context.getSecurityLevelManager()
        curr_lvls = sec_mgr.getSecurityLevelsConfig()
        new_lvls = addTestRole(curr_lvls)
        sec_mgr.updateSecurityLevelsConfig(ImmutableList.copyOf(new_lvls))
        
    except Exception as e:
        logger.info(str(e))
        
    except java.lang.Throwable as t:
	    logger.warn("Java exception occurred", t)

And here's the error being thrown:

com.inductiveautomation.ignition.common.script.JythonExecException: Traceback (most recent call last): File "", line 2, in handleMessage ImportError: Error loading module UserManagement: Traceback (most recent call last): File "", line 3, in ImportError: cannot import name SecurityLevelConfig
at org.python.core.Py.ImportError(Py.java:327)
at com.inductiveautomation.ignition.common.script.ScriptModule.loadModule(ScriptModule.java:79)
at com.inductiveautomation.ignition.common.script.ScriptModule.__findattr_ex__(ScriptModule.java:99)
at org.python.core.PyObject.__getattr__(PyObject.java:957)
at org.python.pycode._pyx66.handleMessage$1(:2)
at org.python.pycode._pyx66.call_function()
at org.python.core.PyTableCode.call(PyTableCode.java:173)
at org.python.core.PyBaseCode.call(PyBaseCode.java:306)
at org.python.core.PyFunction.function___call__(PyFunction.java:474)
at org.python.core.PyFunction.__call__(PyFunction.java:469)
at org.python.core.PyFunction.__call__(PyFunction.java:464)
at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:847)
at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:829)
at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$TrackingProjectScriptManager.runFunction(ProjectScriptLifecycle.java:868)
at com.inductiveautomation.ignition.common.script.message.MessageHandlerRunnable.run(MessageHandlerRunnable.java:121)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.python.core.PyException: ImportError: Error loading module UserManagement: Traceback (most recent call last): File "", line 3, in ImportError: cannot import name SecurityLevelConfig

ah, looking closer at the docs, you need

from com.inductiveautomation.ignition.common.auth.security.level import SecurityLevelConfig

You were importing from gateway.auth instead of common.auth, the former doesn't have anything besides SecurityLevelManager

1 Like

See:

Echoing a valid concern from one of the Ignition developers:

1 Like

Seems I can't trust an LLM's falsifications anymore. Can you explain?

Regarding library scripts:

The 2nd point is especially important when using system.net.httpClient, where you want to be reusing the same instance as much as possible instead of creating a new one each time you do something.

There are also some legacy scoping issues that you get around by having functions defined in the project library script and calling them from events.

Regarding imports:

Basically, the trade off between locking once for an import when first initializing the project library script vs locking every time the function is called. Someone else might be able to explain it in further detail.

1 Like