Perspective Inactive Logout for specific Roles

I'm trying to figure out a way to get Inactivity Logout working but only for specific roles

For example, on our current system, there is some information you can see even when not logged, in.
To Operator equipment you just need to be authenticated
but to do certain things you need the Administrator Role

Someone with an Operator Role will be logged in most of the time, we don't want this auto logging out.
However if someone with Administrator privileges logs in, and then walks away forgetting to logout, we want them to be logged out after X minutes of inactivity.

I've looked into a few ways of doing this

  • Project Properties, has no way to configure auto logout per Role
  • Trying to access lastActivity on a timer triggered gateway script by looping through the open sessions. It appears the lastActivity info isn't returned from the system.perspective.getSessionInfo() function, and I couldn't figure out any info returned through this that I could use
  • I came across these posts about using a propertyChange script on the lastActivity session property. I got part of this working, but not fully.
    • I can't actually seem to get the logout to work. I have it triggering something in the log if it thinks it should be logging out, as well as logging the inactive time to make sure my regex and calculations are correct
    • Below is the on change script set to auto logout after 10 seconds of inactivity. I can see the log events when it thinks it should logout but it never logs out.
def valueChanged(self, previousValue, currentValue, origin, missedEvents):
	from threading import Timer
	
	def runThisLater(session, prev_time):
		logger = system.util.getLogger("Auto Logout Inner")
		curr_time = system.tag.readBlocking(['[System]Gateway/CurrentDateTime'])
		logger.info("{}".format(curr_time))
		
		import re
		unix_ts_regex = '\(([0-9]+)\)'
		try:
			prev_time = int(re.search(unix_ts_regex, str(prev_time)).group(1))
			logger.info("{}".format(prev_time))
			cur_time = int(re.search(unix_ts_regex, str(curr_time)).group(1))
			logger.info("{}".format(cur_time))
			
			inac_time = (cur_time - prev_time) / 1000
			logger.info(str(inac_time))
			if inac_time > 10:
				logger.info("Should be trying to logout now: {}".format(self.session.info["id"]))
				system.perspective.logout(self.session.info["id"], self.session.page.id, "message")
		except:
			logger.error("Unable to properly determine activity time")
	
	Timer(15.0, runThisLater, [self.session, currentValue]).start()

Anyone having any clue what I'm doing wrong...I feel like I'm so close...but it's just not quite working

This is what I use in my session custom props. Apologies for the format...

"propConfig": {
    "custom.security.autoLogout.enabled": {
      "binding": {
        "config": {
          "expression": "{this.props.auth.user.userName} !\u003d None \u0026\u0026\r\nif(tostr({this.custom.security.client.info}) !\u003d \u0027{}\u0027\r\n\t,indexof({this.custom.security.client.info.AutoLogoutDisableUserNames}, {this.props.auth.user.userName}) \u003d -1\r\n\t,True\r\n)"
        },
        "type": "expr"
      }
    },
    "custom.security.autoLogout.inactiveTime": {
      "binding": {
        "config": {
          "expression": "if({this.props.lastActivity} !\u003d None\r\n\t,secondsBetween({this.props.lastActivity}, now(5000))\r\n\t,-1\r\n)"
        },
        "type": "expr"
      }
    },
    "custom.security.autoLogout.logoutUserTrigger": {
      "binding": {
        "config": {
          "expression": "if({this.custom.security.autoLogout.enabled}\r\n\t,{this.custom.security.autoLogout.logoutTimePresetSeconds} - {this.custom.security.autoLogout.inactiveTime} \u003c\u003d 0 \r\n\t,False\r\n)"
        },
        "type": "expr"
      },
      "onChange": {
        "enabled": null,
        "script": "\tid \u003d self.props.id\n\tsystem.perspective.sendMessage(messageType\u003d\u0027logout\u0027, sessionId\u003did, scope\u003d\u0027session\u0027)"
      }
    },
    "custom.security.autoLogout.remainingTime": {
      "binding": {
        "config": {
          "expression": "if({this.custom.security.autoLogout.enabled}\r\n\t,max({this.custom.security.autoLogout.logoutTimePresetSeconds} - {this.custom.security.autoLogout.inactiveTime}, 0)\r\n\t,-1\r\n)"
        },
        "type": "expr"
      }
    }

and in my header:

"messageHandlers": [
        {
          "messageType": "logout",
          "pageScope": false,
          "script": "\t# called by session.custom.security.autoLogout.logoutUserTrigger\n\tsystem.perspective.logout()",
          "sessionScope": true,
          "viewScope": false
        }
      ]

I could probably do it without the message... not sure why I went that route. Maybe I couldn't work out how to log a session out, and system.perspective may not exist in the session's scope?

2 Likes

Hmm, I've taken a quick glance at what you have put, it's an interesting way to try and go about it.

I'm being pulled onto some other projects for a week or so, so I'll have to take a closer look and try my hand at it later.

Thanks.

And if anyone else has some other thoughts or ideas in the mean time, let me know, I can give them all a try and see what works best.

I was tasked with implementing a similar functionality to logout users with 'view-only' access (in my case these were users with either NO roles assigned or just the 'User' role defined) after a given inactivity interval.

I attempted to implemet a scripting solution on a session custom property and ran into the same behaviour you did. Everything would work except the system.perspective.logout() function. I did some digging and apparently there is issue trying to reference the particular session ID of a client when the script is configured on the session (whether that be the lastActivity property change script or a session custom property). Not sure how this works in the backend as I would assume from the client's point of view, the session custom property would have just as much access to the session ID as an active view. It could be the way the system.perspective.logout() function defaults the session ID, not sure.

Also, I wonder if a change script on the lastActivity session property would work for this as the script is only executed on a user's activity. It wouldn't be possible to initiate a logout on inactivity as that value isn't polled...

My solution: configure a custom property on the header of my application (a docked view that is always present accross the client). Configure an expression binding to re-execute a script transform at a given polling rate using the now() expression function (The same thing could be accomplished using a binding and a value changes script, I just prefer having my functionality as centralized as possible). Reference the user roles and last activity session properties each polling cycle and do some logic. I'm sure the code can be cleaned up, but this is what I have working right now.

# Getting the authenticated user's roles
roles = self.session.props.auth.user.roles
# Only execute if user has 'User' role or no roles assigned
if roles[0] == 'User' or not roles:
	# Time conversion function
	def get_time_list(date_time):
		""" Accepts a tz datetime and converts it to an list of 
			the form [HH, MM, SS]. """
		return str(date_time).split(' ')[3].split(':')
	# Inactivity constant [hour, minutes, seconds]
	INACTIVITY_LIMIT = [0, 1, 0]
	# Getting the current time as a list
	currTime = get_time_list(value)
	# Attempt to get the time differential and logout user on a the condition
	try:
		# Getting the lastActivity session property time as a list
		lastActiveTime = get_time_list(self.session.props.lastActivity)
		# finding the time differential
		dt = [int(currTime[i]) - int(lastActiveTime[i]) for i in range(3)]
		# Logout user if inactivity is greater than the limit
		if dt[0] >= INACTIVITY_LIMIT[0] and dt[1] >= INACTIVITY_LIMIT[1] and dt[2] >= INACTIVITY_LIMIT[2]:
			system.perspective.logout()
		return 'Inactive time: {}'.format(dt)
	except:
		pass
return 'User not view only.'