# Update Security Level Configuration via Script

**URL:** <https://forum.inductiveautomation.com/t/update-security-level-configuration-via-script/112369>\
**Category:** Ignition\
**Tags:** ignition81, perspective, scripting\
**Created:** [November 21, 2025, 9:20pm UTC](https://forum.inductiveautomation.com/t/update-security-level-configuration-via-script/112369 "2025-11-21T21:20:32Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![nolan.pratt](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nolan.pratt/32/78503_2.png) [@nolan.pratt](https://forum.inductiveautomation.com/u/nolan.pratt)\
**Post date:** [November 21, 2025, 9:20pm UTC](https://forum.inductiveautomation.com/t/update-security-level-configuration-via-script/112369/1 "2025-11-21T21:20:32Z")

</div>

I am wondering how I can access [updateSecurityLevelsConfig](https://files.inductiveautomation.com/sdk/javadoc/ignition83/8.3.0/com/inductiveautomation/ignition/gateway/auth/security/level/SecurityLevelManager.html#updateSecurityLevelsConfig(com.google.common.collect.ImmutableCollection)) through the [SecurityLevelManager](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.3/com/inductiveautomation/ignition/gateway/auth/security/level/SecurityLevelManager.html) interface to programmatically import a new Security Levels tree within a [Gateway Script](https://www.docs.inductiveautomation.com/docs/8.1/platform/scripting/scripting-in-ignition/gateway-event-scripts#calling-message-handlers).

Anytime I attempt to import the [SecurityLevelConfig](https://files.inductiveautomation.com/sdk/javadoc/ignition83/8.3.0/com/inductiveautomation/ignition/common/auth/security/level/SecurityLevelConfig.html), 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:

```Python
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))

```

---

<div class="post-metadata">

**Author:** ![ryan.white](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan.white/32/73193_2.png) [@ryan.white](https://forum.inductiveautomation.com/u/ryan.white)\
**Post date:** [November 21, 2025, 9:38pm UTC](https://forum.inductiveautomation.com/t/update-security-level-configuration-via-script/112369/2 "2025-11-21T21:38:43Z")

</div>

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:

```python
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

```python
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.

---

<div class="post-metadata">

**Author:** ![nolan.pratt](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nolan.pratt/32/78503_2.png) [@nolan.pratt](https://forum.inductiveautomation.com/u/nolan.pratt)\
**Post date:** [November 21, 2025, 10:17pm UTC](https://forum.inductiveautomation.com/t/update-security-level-configuration-via-script/112369/3 "2025-11-21T22:17:06Z")

</div>

> [@ryan.white](#):
>
> is this code LLM generated?

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()`:

```Python
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:

```Plaintext
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

```

---

<div class="post-metadata">

**Author:** ![ryan.white](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan.white/32/73193_2.png) [@ryan.white](https://forum.inductiveautomation.com/u/ryan.white)\
**Post date:** [November 21, 2025, 10:23pm UTC](https://forum.inductiveautomation.com/t/update-security-level-configuration-via-script/112369/4 "2025-11-21T22:23:09Z")

</div>

ah, looking closer at the docs, you need

```python
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`

---

<div class="post-metadata">

**Author:** ![ryan.white](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan.white/32/73193_2.png) [@ryan.white](https://forum.inductiveautomation.com/u/ryan.white)\
**Post date:** [November 21, 2025, 10:38pm UTC](https://forum.inductiveautomation.com/t/update-security-level-configuration-via-script/112369/5 "2025-11-21T22:38:00Z")

</div>

> [@nolan.pratt](#):
>
> Of course!

See:

> [@ApexChart Using onMouseEnter and onMouseLeave events](https://forum.inductiveautomation.com/t/apexchart-using-onmouseenter-and-onmouseleave-events/111647/4):
>
> In the future, declare this up front, for any LLM-generated material. Per the [forum rules](https://forum.inductiveautomation.com/faq).

Echoing a valid concern from one of the Ignition developers:

> [@Ignition Edge sends emails but no attachments](https://forum.inductiveautomation.com/t/ignition-edge-sends-emails-but-no-attachments/111666/7):
>
> **Don't use LLMs to create Ignition code blindly**.
> 
> If you don't already know what you're doing, an LLM will happily give you a mountain of _terrible_ code.

---

<div class="post-metadata">

**Author:** ![nolan.pratt](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nolan.pratt/32/78503_2.png) [@nolan.pratt](https://forum.inductiveautomation.com/u/nolan.pratt)\
**Post date:** [November 21, 2025, 11:03pm UTC](https://forum.inductiveautomation.com/t/update-security-level-configuration-via-script/112369/6 "2025-11-21T23:03:19Z")

</div>

> [@ryan.white](#):
>
> 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:

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

---

<div class="post-metadata">

**Author:** ![ryan.white](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan.white/32/73193_2.png) [@ryan.white](https://forum.inductiveautomation.com/u/ryan.white)\
**Post date:** [November 21, 2025, 11:20pm UTC](https://forum.inductiveautomation.com/t/update-security-level-configuration-via-script/112369/7 "2025-11-21T23:20:14Z")

</div>

Regarding library scripts:

> [@How to develop/debug scripts on gateway](https://forum.inductiveautomation.com/t/how-to-develop-debug-scripts-on-gateway/81319/17):
>
> The reasons are, to start:
> 
> - Functions in the project library are re-usable. When carefully designed, they can be used in many parts of your application.
> - Functions in the project library can re-use constants and cached values that are stored in named variables **outside** the `def`. This **does** reduce execution time, as those variables persist from call to call.
> - Functions in the project library are maintenance-friendly, particularly that they are stored in the filesystem in plain text as `code.py`, and are therefore very friendly to source code control, like `git`.

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:

> [@What external tools do you use for Ignition development, and why?](https://forum.inductiveautomation.com/t/what-external-tools-do-you-use-for-ignition-development-and-why/93759/71):
>
> Be aware that `import` always takes the interpreter's modules lock

> [@What external tools do you use for Ignition development, and why?](https://forum.inductiveautomation.com/t/what-external-tools-do-you-use-for-ignition-development-and-why/93759/71):
>
> All use of `import` should be in project library scripts at the outermost level, never inside function definitions, or performance will suffer.

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.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [November 21, 2025, 11:40pm UTC](https://forum.inductiveautomation.com/t/update-security-level-configuration-via-script/112369/8 "2025-11-21T23:40:04Z")

</div>

> [@ryan.white](#):
>
> further detail.

One of the fundamental differences between CPython and Jython is that the latter is fully and truly **multi-threaded**. Jython has no GIL.

That `import` always takes a lock doesn't create additional lock contention in CPython because the contention is on CPython's GIL.

Ignition performs auto-importing of project library scripts on first use after a project start or restart (edits). That auto-import is _ **lockless** _, for huge performance wins under busy event load. (But just the fast path is lockless in v8.3, fixing a [longstanding bug](https://forum.inductiveautomation.com/t/ign-6503-project-library-script-initialization-races/98636).)

This is a huge performance win in Ignition because many, many events firing at high rates will crush that interpreter-wide import lock.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [November 21, 2025, 11:57pm UTC](https://forum.inductiveautomation.com/t/update-security-level-configuration-via-script/112369/9 "2025-11-21T23:57:03Z")

</div>

> [@nolan.pratt](#):
>
> It's honestly much more efficient to prompt

And yet, here you were, up a blind alley with no idea how to proceed...

A vibe-coded SCADA project seems about as "efficient" as it does "maintainable", but that's just my two cents late on this Friday afternoon.

---

<div class="post-metadata">

**Author:** ![andrew.budaiev](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/andrew.budaiev/32/72921_2.png) [@andrew.budaiev](https://forum.inductiveautomation.com/u/andrew.budaiev)\
**Post date:** [November 22, 2025, 12:05am UTC](https://forum.inductiveautomation.com/t/update-security-level-configuration-via-script/112369/10 "2025-11-22T00:05:32Z")

</div>

All roads lead to...Ignition forum.
