OMERO security system

The OMERO security system is intended to be as transparent as possible while permitting users to configure the visibility of their data. For the user, this means that with no special actions, data and metadata created will be readable by both members of the same group and by other users, but will be writable by no one, comparable to a umask of 755 on Unix. For the developer, transparency means that there is little or no code that must be written to prevent security violations, and simple mechanisms for allowing restricted operations when the time comes.

Other links which may be of use:

Concepts

Several concepts and/or components from our and other code bases play a role in OMERO security:

Hibernate Listeners and Events
listeners and events are the two extension points provided by Hibernate for responding to and influencing internal actions. Essentially any method on the org.hibernate.Session interface has a corresponding event, and almost the same is true for the interceptor. Additionally interceptors can change the state of the objects before INSERT and UPDATE, and after SELECT.
Hibernate Filters
filters are a mechanism for injecting SQL clauses into the SELECT statements generated by Hibernate. Similar to listeners and events for write actions, filters allow us to extend Hibernate functionality with our own logic.
Handler/interceptor
as outlined in Aspect-oriented programming, OMERO makes extensive use of method interceptors to relieve the developer of some coding burden. Transactions, session management, and, naturally, security are handled largely by our interceptors (or “handlers”).
Events
Every write action produces an Event in the database. This database contains several EventLogs which specify exactly what was created or altered during that specific event.

Participants

Now, with the concepts cleared up, we can take a look at all of the concrete source artifacts (“participants”) which are important for security.

Top-level and build

omero.properties
contains login and connection information for the database and OMERO.
build.properties.example
contains the default root password. This can be overridden with your own etc/local.properties file.
hibernate.properties
contains default connection information for the database, this includes the user name and if necessary the user password. These values can be overridden in local.properties.
omero.properties
contains a default user group, event type, and connection information for logging in from the client side, if no Login or Server is specified to ServiceFactory. These values can be overridden in local.properties.
mapping.vm
specifies the default permissions that all objects will have after construction, as well as attaches the security filter to all classes and collections.
data.vm
used by DSLTask to generate psql-footer.sql which is used to bootstrap the database security system (root et al).
common/build.xml
contains an ant target (adduser) which will create a user and empty password from the command line. This target can also be called from the top-level (java omero adduser).

Client and common

the server uses the information in /etc/local.properties to create a Login
object. If no Login, Server, or Properties is provided to the ServiceFactory constructor, the empty properties defined in ome/config.xml is used.
IAdmin.java
main interface for administering accounts and privileges. See OMERO admin interface for more.
ITypes.java
only related to security by necessity. The security system disallows the creation of certain “System-Types”. Enumerations are one of these. ITypes, however, provides a createEnumeration method with general access.
GraphHolder.java
all model objects (implementations of IObject have a never-null GraphHolder instance available. This graph holder is responsible for various OMERO and Hibernate internal processes. One of these is the exchange of Tokens. For the server, the existence of a special token within the GraphHolder grants certain privileges to that IObject. This logic is encapsulated within the SecuritySystem.
Details.java
contains all the fields necessary to perform access control, such as owner, group, and permissions.
Permissions.java
representation of rights and roles. For more information, see Groups and permissions system.
Token.java
an extremely simple class (“public class Token {}”) which is only significant when it is equivalent (“==”) to a privileged Token stored within the SecuritySystem.
IEnum.java
the only non-access control related types which are considered “System-Types” are enumerations. IEnum is a marker interface for all enumerations and creation of IEnum implementations can only be performed through ITypes.
SecurityViolation.java
the exception thrown by the OMERO security system at the first hint of misdoings.
Principal.java
an Omero-speciific implementation of the java.security.Principal interface. Carries in addition to the typical name field, information about the user group, the event type, and the session umasks.

meta.ome.xml

End-to-end

Build system

Security starts with the build system and installation. During the generation of the model (by the DSLTask), a sql script is created called “data.sql”. After ddl.sql creates the database, data.sql bootstraps the security system by creating the initial (root) experimenter, and event, and then creates the “system” group and the “user” group. It then creates a password table and sets the root password to “ome”. (It also creates all of the enumeration values, but that is unimportant for security).

Note

The password table is not mapped into Hibernate, and is only accessible via the OMERO admin interface.

Client-side

To begin the runtime security process, a user logs in by providing a Login and/or a Server instance to ServiceFactory. These types are immutable and their values remain constant for the lifetime of the ServiceFactory. The user can also set the umask property on ServiceFactory_. This value is mutable and can be set at anytime.

The values are converted to java.util.Properties which are merged with the properties from the *.properties files from /etc to create the client OmeroContext (also known as the “application context”). The context contains a Principal and user credentials (password etc.) which are associated with the thread before each method execution in a specialized TargetSource. Finally, these objects are serialized to the application server along with the method arguments.

Application server

The application server first performs one query (most likely SQL) to check that the credentials match those for the given user name. A second query is executed to retrieve all roles/groups for the given user. If the roles returned are allowed to invoke the desired method, invocation continues with the queried user and roles stored in the InvocationContext.

Server code

Execution then passes to OMERO code, specifically to the interceptors and lifecycle methods defined on our session beans. This intercepting code checks the passed Principal for OMERO-specific information. If this information is available, it is passed into the SecuritySystem through the login method. Finally, execution is returned to the actual bean which can either delegate to OMERO services or perform logic themselves.

Interceptors

All calls to the delegates (and in the future all calls on the session beans) are also caught intercepted by Spring-configured interceptors. These guarantee that the system is always in a valid and secure state. In stack order they are:

  • the service handler, which handles logging and checks all arguments against ServiceInterface annotations;
  • the proxy handler, which after execution, removes all uninitialized Hibernate objects to prevent exceptions (special logic allows this to happen See unloaded objects);
  • the transaction handler, which binds a transaction to the thread,
  • the session handler, which uses the now prepared transaction to initialize either a new or a cached (in the case of stateful session beans) session and also bind it to the thread;
  • and finally, the event handler, which performs what one might actually consider login. It instatiates Experimenter, ExperimenterGroup, and Event objects from Hibernate and gives them a special Token so that they can authenticate themselves later to the SecuritySystem and turns session read security on for the entirety of execution below its frame.

Services

Finally execution has reached the OMERO services and can begin to perform logic. Because of these layers, almost no special logic (other than eviction and not calling write methods from within read methods. see #223) needs to be considered. There are, however, a few special cases.

IQuery (within the application server), for example will always return a graph of active Hibernate objects. Changes to them will be persisted to the database on flush.

IUpdate, on the other hand, does contain some logic for easing persistence, though this will eventually be ported to the Hibernate event system. This includes pre-saving the newly created event and the work of UpdateFilter like reloading objects unloaded by the proxy handler (above).

Finally, IAdmin is special in that it and it alone access the non-Hibernate password data store and even access application server APIs (like JMX) in order to make authentication and authorization function properly.

Hibernate

Once execution has left this service layer, it enters the world of Hibernate ORM. Here we cannot actively change functionality but only provide callbacks like the OmeroInterceptor and EventListeners. The OmeroInterceptor instance registered with the Hibernate SessionFactory (via Spring) is allowed for calling back to the often mentioned SecuritySystem to determine what objects can be saved and which deleted. It also properly sets the, for a user mostly unimportant, Details object. The EventListeners are more comprehensive than the OmeroInterceptor and can influence almost every phase of the Hibernate lifecycle, specifically every method on the Session interface.

The event listeners which implement AbstractSaveEventListener (i.e. MergeEventListener, SaveOrUpdateEventListener, etc.) are responsible for reloading unloaded objects (and will hopefully take this functionality fully from IUpdate) and provide special handling for enums and other system types. There are also event listeners which are the equivalent of database triggers (pre-update, post-delete, etc.) and these are used for generating our audit log.

So much for write activities. Select queries are, as mentioned above, secured through the use of Hibernate filters which add join and where clauses dynamically to queries. For example an HQL query of the form:

select i from Image i

would be filtered so that the current user does not receive references to any objects with reduced visibility:

select i from Image i where ( current_user = :root OR i.permissions = :readable )

The actual clauses added are much more complex and are added for each joined entity type (i.e. table) which apears in a query.

select i from Image i join i.defaultPixels p

would contain the “( current_user = :root …)” clause twice.

Currently, subqueries are an issue in that the clauses do not get added to them. This may cause consternation for some particular queries.

Security system

All of this is supported by an implementation of the SecuritySystem interface which encapsulates all logic regarding security. It also hides as much as it can, and if not specifically needed should be ignored. However, before you attempt to manually check security, by all means use the security system, and for that, it may need to be acquired from the server-side OmeroContext. Currently, there is no client-side security system. See #234.

The OMERO security system and its current only implementation BasicSecuritySystem? are somewhat inert and expect well-defined and trusted (see #235) methods to invoke callbacks during the proper Hibernate phase.

Logging in (client-side)

When using the client library and the ServiceFactory, logging in is trivial. One need only set several System properties or place them in an omero.properties file somewhere on the classpath. Internally, Spring takes the System properties and creates an ome.system.Principal instance. This is then passed to the server on each invocation of a proxy obtained from JNDI.

Logging in (server-side)

Much of this infrastructure is not available to server-side code (no ome/client/spring.xml, no ServiceFactory, etc.). As such, the Principal needs to be manually created and provided to the server-side SecuritySystem.java.

Basically it amounts to this:

Principal p = new Principal( omeroUserName, omeroGroupName, omeroEventTypeValue );
securitySystem.login( p );

This must be run otherwise the EventHandler will throw a security exception.

Note

The code above is being run in a secure context (i.e. you are root). Please be careful.