New Tag Permissions Model based on Security Levels

The 5/8/2020 Nightly Release of Ignition 8.0.13 introduces an overhaul to Tag Security. The primary goal of this overhaul was to get the Tag Permissions model to start using Security Levels, a powerful new hierarchical permissions model introduced in Ignition 8 and previously only used in Perspective. This overhaul is also an important step to make as we transition other parts of Ignition to use Identity Providers and Security Levels.

Some of the changes you will see:

  • The Security property set of a tag has 3 options:

    • Read Permissions: Defines the security levels required in order to read values from a tag
    • Read Only: Defines whether a tag is read-only or writable
    • Write Permissions (hidden if Read Only is enabled): Defines the security levels required in order to write values to a tag
  • A new configuration screen for Read and Write permissions which resembles the Perspective permissions configuration experience:

    • You no longer have to type out role names or security zones. The checkbox tree you are presented with will show you all of the security levels configured in the Gateway Config > Security > Security Levels page
    • You have the ability to set security by Role Only, Role + Security Zone or Security Zone Only.
    • You have the ability to require any or all the security levels you have selected
  • Any existing Tag Security will be migrated to the functional equivalent in the new model on upgrade. The same goes for tag imports from previous versions.

We hope that the changes benefit you and we welcome all feedback. If you do find an issue that you feel needs addressed, please let us know.

8 Likes

I’m trying to port my old function for determining whether a tag is readable or writable to Ignition 8 (see Role-based tag permission vs client visualisation)

The permissions model seems a lot more complicated though (deeper nested JSON, or-ed and and-ed together).

Would there be an easier way to check if the current user is able to read or write a tag?
Or is there something coming to bind enabled/editable/visible/… properties to tag readability? Like a special expression perhaps?

It seems like a lot of work to get it right with all special cases, if that functionality could be added in one of the next versions.

+1

I have same needs.
I have converted all my generated udt with the new tag permission format for Ignition 8.0.13+
For checking if write operation is authorized for a tag with scripting, you will had to read the tag properties writePermissions and readOnly and use something ugly like that to obtain roles and compare it to the logged user roles...

def getRoleFromTagPermission(permission):
	"""	
	Example :	
	  "writePermissions": {
		"type": "AnyOf",
		"securityLevels": [
		  {
			"name": "Authenticated",
			"children": [
			  {
				"name": "Roles",
				"children": [
				  {
					"name": "BIL_param",
					"children": []
				  },
				  {
					"name": "GTC_visu",
					"children": []
				  }				  
				]
			  }
			]
		  }		  
		"readPermissions": {
		  	"type": "AnyOf",
		  	"securityLevels": []
		}		  
	"""
	global strLib
	strFct="getRoleFromTagPermission"	
	try:
		result = []
		if ((permission != None) and (permission != "")):
			#<type 'com.inductiveautomation.ignition.common.auth.permissions.PermissionsConfig$AnyOfPermissionsConfig'>	
			#if type(permission)==AnyOfPermissionsConfig:
			json = permission.toJsonTree()
			if json != None:
				if "securityLevels" in json:
					securityLevels = json["securityLevels"]
					if securityLevels != None:
						if len(securityLevels)>0:
							roles = securityLevels[0]["children"][0]["children"]
							for role in roles:
								result.append(role["name"])
	except:
		shared.commun.logger.logERROR(strFct=strFct,strLib=strLib,strLog="GTC_Tag")
	return result

@jspecht
If there is a way to obtain the securitylevels of the user, there is an isAuthorized function to check the permission.

This works for me in Perspective:

	from com.inductiveautomation.ignition.common import TypeUtilities
	from com.inductiveautomation.ignition.common.auth.security.level import SecurityLevelConfig
	from com.inductiveautomation.ignition.common.auth.permissions import PermissionsConfig
	
	securityLevelsProp = self.session.props.auth.securityLevels
	
	securityLevels = []
	for securityLevel in securityLevelsProp:
		securityLevels.append(SecurityLevelConfig.fromJsonTree(TypeUtilities.pyToGson(securityLevel)))
	
	securityLevels = SecurityLevelConfig.merge(securityLevels)
	
	print securityLevels
	
	readPermissions = system.tag.readBlocking(['[default]LongExprTag.readPermissions'])[0].value
	writePermissions = system.tag.readBlocking(['[default]LongExprTag.writePermissions'])[0].value
	isReadOnly = system.tag.readBlocking(['[default]LongExprTag.readOnly'])[0].value
	
	print readPermissions
	print writePermissions
	print isReadOnly
	
	hasReadPermissions = readPermissions.isAuthorized(securityLevels)
	hasWritePermissions = writePermissions.isAuthorized(securityLevels)
	
	print hasReadPermissions
	print hasWritePermissions
	

output:

INFO   | jvm 1    | 2020/06/19 11:20:03 | [{"name":"Authenticated","children":[{"name":"Roles","children":[{"name":"Administrator","children":[]}]}]}, {"name":"SecurityZones","children":[]}]
INFO   | jvm 1    | 2020/06/19 11:20:03 | {"type":"AllOf","securityLevels":[]}
INFO   | jvm 1    | 2020/06/19 11:20:03 | {"type":"AllOf","securityLevels":[{"name":"Authenticated","children":[]},{"name":"Foo","children":[{"name":"Bar","children":[{"name":"Baz","children":[]}]}]}]}
INFO   | jvm 1    | 2020/06/19 11:20:03 | False
INFO   | jvm 1    | 2020/06/19 11:20:03 | True
INFO   | jvm 1    | 2020/06/19 11:20:03 | False

Granted it’s not very straightforward. I’ll see what we can do to improve this.

Thanks a lot,
but for Vision, how to obtain the securityLevels of the logged-in user ?

Assuming you have a list of the current vision user’s roles and zones, you could do something like this:

roles = ["Administrator", "Operator"]
zones = ["ZoneA", "ZoneB"]

securityLevelPaths = []

for role in roles:
	securityLevelPaths.append(["Authenticated", "Roles", role])

for zone in zones:
	securityLevelPaths.append(["SecurityZones", zone])

securityLevels = SecurityLevelConfig.fromPaths(securityLevelPaths)

At this point, you have securityLevels, so checking permissions would be like the first example I gave above.

2 Likes

Perfect :+1:
If we don’t use security zones, we can skip ?

securityLevelPaths.append(["SecurityZones", zone])

Yes you can skip the security zones, but if your permissions require a specific security zone, isAuthorized() will return false.

Edit: if you do not provide a SecurityZones node or if you do provide it but it has no children, we assume the user falls back to the “Default” security zone. So if your permissions does require the “Default” zone then it could return true as long as the other security levels are satisfied in the permissions.

2 Likes