I have an RPC endpoint I'd like to protect by checking security levels/zones/roles etc. I wanted to check what the recommended way of handling this would be?
I'm thinking I'll use the GatewayConnectionManager and ClientUserUtilities to pass the entire User object, but is there an easier way to do this based on a session ID or something on the gateway when I receive an RPC request?
Also I haven't done much research on this, but what's the recommended way to check authorization with the new "Security Levels" that you guys have implemented?
You get a ClientReqSession in your RPC handler invocation:
From the session you can retrieve the user object: ClientReqSession.User clientUser = (ClientReqSession.User) session.getAttribute(ClientReqSession.SESSION_USER);
If you create a class instance of your RPC implementation to carry this Session, you probably will want to use a cache of those instances instead of creating one on every RPC call. But, be careful with lifetimes. Both the cache and the implementation instance need to hold the Session with a weak reference or you will prevent the session from being garbage collected after the client/designer closes.
Is there a way to get an AuthenticatedUser from a WebAuthUser? I guess I'm looking for a way to get Roles/Levels/Zones from a WebAuthUser in the end. Am I guaranteed to have a SecurityContext thread local whenever I have a WebAuthUser thread local set?
How are you currently obtaining a WebAuthUser? It looks like there's a sibling class com.inductiveautomation.ignition.common.auth.web.WebAuthUserContext that has zone information. As I understand things, WebAuthUser is the 'base' class for an IdP login, where AuthenticatedUser is the 'base' class for "classic" login using user sources, so I don't think the two are interchangeable in the way you're describing.
or by looking up a session-id in the REST API for my app.
I guess let me back up - I'm really looking for a way to build a common/custom SecurityContext object that I can use that will have Zones/Roles/Levels, the User object, and any information required for the audit log (hostname, designer/client/perspective enum etc.). But I'd like to try to build one common object for both RPC and Perspective if possible...
You'll have to create your own facade, with implementations for both a WebAuthUserContext (IdP auth strategy) and AuthenticatedUser (classic user source auth strategy).
We had to do that with Vision Client and Designer sessions when we introduced IdP support for those systems in 8.1. The facade we introduced is interface com.inductiveautomation.ignition.gateway.clientcomm.ClientReqSession.User:
Vision Client and Designer sessions were originally designed for Classic auth so the facade interface getUser() method returns an AuthenticatedUser, which is a straight delegation to the underlying AuthenticatedUser that a ClassicUser wraps and IdpUser returns an AuthenticatedUser instance adapted from the WebAuthUserContext instance.
There is a similar adaptation for ClassicUser#getSecurityLevels where we have to build the security levels from the roles and zones set on the AuthenticatedUser, where as an IdpUser delegates to the WebAuthUserContext#getSecurityLevels method since IdP auth strategy speaks security levels natively.
Note: getUser() returns an Optional of AuthenticatedUser because in the IdP auth strategy, it's possible that the user might be authorized to access a project without logging in. For example: if the project is Public or only protected with security zones, the user may not need to log in to access the project...
Last (hopefully) question - is there any way I can detect if a script is being called from the gateway scope (tag change script etc. vs perspective session) so that I can inject a system security context? Where do you guys generally use a "system context" inside of Ignition? Just so I can keep my code consistent...
Assuming you are referring to the tag system's SecurityContext (com.inductiveautomation.ignition.common.tags.model.SecurityContext) - We typically assign system context to privileged automated things configured by privileged admins / designers. Think SFCs, Transaction Groups, Gateway timer scripts, etc. They are non-human things that are configured to perform some kind of tag operations. They are not associated with a human via some session (such as Gateway Web Interface, Vision Clients, Perspective Sessions, etc).
Is there any way I can detect if a script is being called from the gateway scope (tag change script etc. vs perspective session) so that I can inject a system security context?
I'm not sure I understand the use case... you're dealing with an RPC function, right? So these are limited to Vision Client Sessions and Designer Sessions. You have everything you need from ClientReqSession that is passed to you. A script shouldn't be calling into your RPC function...
Edit: to clarify, your RPC handler is Gateway-scoped - you provide an instance of it by overriding GatewayModuleHook#getRPCHandler. So you are running in the context of the Gateway. The Platform will provide the ClientReqSession to you, which is essentially a Vision Client or Designer session (you can check which one by calling ClientReqSession#isDesigner).
You can get an instance of the ClientReqSession.User object that I mentioned above which is associated with the given session by calling:
I guess what I'm trying to do here is secure a service endpoint (RPC function calls the service class method), but I would like that same method to be available for gateway scripts.
So I'll have something like system.tamaki.logSomething that could be called from the client or from a gateway script, and if it's a client/designer calling it I want to make sure they have the correct roles/levels etc., but if it's a gateway event script that thread local wouldn't exist.
Well your RPC function handler in the Gateway will know who the user is from the session (details above), so you will know how to create your SecurityContext from that code path.
In the case where you have a scripting function invocation on the Gateway side, you could get the current value of SecurityContext.THREAD_LOCAL.get(). If there is a value present, then the system from which the script was invoked had set the SecurityContext for you. If it is not set (null), then you have to decide what the default value should be (either system context or empty context in most cases).
Ok last last question (maybe). I'm doing some HTTP basic auth for my app, and would like to authenticate that user and put them in some sort of context... 2 Parts.
How can I authenticate a user against an idp?
Which auth context would be appropriate for this situation?
Unfortunately basic auth and IdP’s don’t go together.
You can’t authenticate a user against an IdP, you can only redirect the user to the IdP and have it inform you of the result.