In the Grails framework every class can access the Domain Classes (and therefore the database records) by using the Hibernate Query Language (HQL) directly. This means a security layer with Access Control Lists can easily be circumvented. The development has to be stirred into a direction that guarantees that security vulnerable code cannot be included and possible problems are noticed early. All access to the Domain Classes has to be done through service methods ensuring the access rights. The security itself has to always be verified by appropriate Unit and Integration tests.
Moreover, JUMMP has to include a plugin interface to ease customisation and external contributions. As Grails already provides a plugin mechanism this can be used and tailored to the needs of JUMMP. As a matter of fact a Grails plugin does not solve the mentioned security issues. A plugin has full access to all services and DOM objects.
Problem of Protecting Domain Objects
As Grails makes heavy use of the dynamic programming features of the Groovy language, several noteworthy issues arise.
In order to use the dynamic property access it is not possible to have private or package private properties in the Domain classes. This means that every class in the application can access the properties of DOM objects directly and modify them. A security layer can always be circumvented by accessing the DOM objects directly.
To a certain degree the application could be protected by the use of meta programming and aspect oriented programming. Though these techniques do not allow to get information about the caller in the intercepted method. So it is not possible to block access based on package or class names.
A package structure could be used but does not add any additional security as external contributed plugins could easily add additional classes to the package structure.
The Grails plugin architecture allows a plugin to have access to all parts of the application. A JUMMP plugin should not have direct access to the Domain Object layer. As explained above, this cannot be ensured. A solution is to use some convention for the plugins' interface. So a convention could be that a plugin may not introduce new domain classes. The application bootstrap could know all domain classes the core application provides and unload all plugins which add additional classes to the domain layer.
It is most important that the views and controllers do not have direct access to the domain objects and the VCS containing the model files. The view layer must only be allowed to communicate with the services provided by the core application. Therefore the views are separated from the core by running in a different application. The communication between core and view application has to be through an IPC mechanism such as JMS.
The view layer can only communicate with the service methods exported by the core for JMS. This ensures that the view can only access a subset of methods and that those are always secured. If a plugin has a direct counterpart in the core application it can use JMS to communicate directly. Of course such a core plugin could again circumvent security. Plugins included into the core have to go through a peer code review process.
Two Applications Approach
As described above, each and every component in a Grails application has access to the database. This would allow authors of plugins to directly access the database layer in the views. Grails recommends to use the Model-View-Controller pattern (MVC), so that only a model is passed to the view from the controller. Data access should not happen in a view. If it happens in the view the security can be circumvented.
Not all parts of the core application should be used by the plugins. There are certain parts which are out of bounds, like security. The core provides a high-level API to access all relevant data for the plugins. If a plugin needs data currently not yet provided by the API, the proper approach is to extend the API and not to access the database directly.
How it is done
The communication layer between the plugins and the core uses JMS. This ensures that only the API provided for plugins can be accessed by the plugins in the view layer. Core internal services are only available to the core for direct method calls. Nevertheless the plugins are able to access the core internal services or even the database directly if the plugins live in the same application.
This problem can be circumvented by using two web applications. The core is running in one application and the plugins and views are running in another application. The only way to communicate between core and plugins is by using the JMS interface. That way completely prevents plugins to access core-internal data.
The MVC pattern is enforced to all authors of the application. A view cannot just query the database and is also not able to alter the data. All data processing has to be done in the core through the available API calls. This way the security can be ensured. If the API ensures that only logged-in users can access their private data, the plugins cannot expose private data.
External contributions do not have to be reviewed for security issues in plugins or views. Without this approach each commit could introduce a security vulnerability (both intended and unintended).
Two web applications have to be deployed, which makes the build and setup process more difficult.
This approach might influence load-balancing setups as used by the EBI. This problem could be solved by only load-balancing the entry points (two servers) and running just one server with the core. The load-balanced web servers talk via the same JMS bus to the core application. It has to be investigated if the main load is on the view or the core, that is if it is ok to not balance the core. Though balancing the core can result in problems when accessing VCS or database (concurrent access).
Another possible problem is authentication using Spring Security. The Authentication is set in the user's session which means the view web-application. But the core application needs access to the Authentication for ACL. Also username/password verification has to be done in core because the database has to be accessed.
This problem is solved by using a custom AuthenticationProvider which performs the authentication process in the core application. The Core application generates a unique hash for the authenticated user and adds this hash to the Authentication. In each further request the user is identified by the hash value and the core application takes care of ensuring that the thread's [[http://static.springsource.org/spring-security/site/apidocs/org/springframework/security/core/context/SecurityContext.html|SecurityContext}} is populated with the Authentication identified by the hash.
In order to enforce the plugin architecture all view functionalities should be implemented as plugins. Therefore also the view rendering process has to be plugin aware. It is not sufficient to only allow plugins to add new controllers and views, they have to be able to hook into other plugins and core pages.
Example: Showing a Model
The view to render a Model is invoked. The base view contains a tag to render the model. The tag calls each plugin which provides the same tag to collect further data. The current context is passed to each plugin, which consists of the model to be rendered and the JSON structure containing all collected information. Each plugin is so to say asked if it wants to add further information to the rendering process.
There could be a "Comment" plugin which shows additional comments and a button to create a comment. The plugin provides the tag to render the model and because of that will be called. The plugin fetches the additional comments for the current model from core and adds those to the data section of the JSON structure passed to the plugin. In the meta section of the JSON structure the plugin adds information to render a button for creating a comment.
JUMMP consists of a core and a series of plugins. The main functionality of the core is to provide secure access to the models for the plugins. All access to domain objects has to be part of the core. This gives the following important tasks to the core: Domain objects Access to VCS Service methods to manipulate the DOM objects Export of service methods via JMS * Loading the plugins
The views are, as discussed above, separated from the core and use the JMS interface to query for data. Nevertheless also the views provide a "core" which takes care of loading the rendering plugins and distribute rendering to those plugins.
Most plugins for JUMMP will live in the view layer and are not allowed to alter the database that is adding additional tables. If a plugin needs to store additional information another plugin in the core is required.
To alter the database three possible solutions are available: Extend the core Use a plugin which implements the functionality * Combination of 1 and 2:
In all three cases the JMS API has to be extended. This is possible by either adding the required API calls to the service or to add another API entry (only useful in the case of a plugin). The view plugin can then talk directly to the core plugin.
Of course code going into the core has to reviewed for security issues.
Which of the three possible solutions is chosen has to be decided on a case-by-case study. For some functionality it might be desirable to have it directly in the core other functionality just adding new domain objects might be better completely separated in a plugin and for some features it might be desirable to have the data access secured in the core but to keep the actual implementation outside of the core, e.g. if different implementations are possible (think of search).
As our system is incrementally develops it is important to track the architecture as implemented. This serves serves several purposes: provides a high level introduction to components and their coupling to new developers, allows better planned change to system components, and enables early flagging of conflicts between the architecture as envisioned, and as implemented. To that end, we are following the Focus methodology to document the architecture of the Jummp prototype.
The first step of the process involves generating a class diagram of the system. While there are tools that support automatic class diagram generation (e.g. ArgoUML) for certain languages (e.g. Java), the flexible dependency injection mechanisms of Grails make this a more arduous process. The class diagram below was constructed by hand. As there are roughly 650 classes, the manual process is susceptible to errors, something that should be borne in mind when interpreting the diagram. Furthermore, package names are abbreviated: although each class corresponds to a groovy or java file, common sense contractions are used where referencing them in the UML model.
Following the Focus process, the classes were then arranged into strongly related components, which generates the groupings below. It is possible to see the main components and the coupling between them. The next step will be to describe in terms of the components identified the execution of specific use case scenarios. This section will be updated as further architecture documentation is generated from application of Focus.
The ArgoUML file for the model above can be downloaded here.
The core and plugin concept is inspired by existing successful Open Source applications using such an approach. Notable for further information about such architectures are the following projects:
- Compiz is a plugin based window manager using a very thin core which only loads the plugins. All functionalities are encapsulated in plugins which can depend on each other and conflict with each other. The documentation of Compiz is rather bad, but some information can be found in the Project Wiki.
- KWin is the window manager of KDE Plasma Workspaces. In opposite to Compiz it consists of a monolithic core but supports plugins which can replace functionality from the core. Documentation about the design is not available except in the source code itself and a small Tutorial.
- Plasma is a framework to create applet (plugin) centric workspaces for devices such as Desktop PCs, Netbooks, Tablets and smartphones. It uses IPC mechanisms for the communication between plugins and core and allows to have plugins inside plugins. Rendering is passed to each plugin to generate a complete view.
- Akonadi is a framework for exchange of PIM-related data. The core is a cache of PIM data, which fetches the actual data from various resources and distributes it to interested agents for processing. Resources and agents can be considered as plugins. Communication between all parts is done via IPC mechanisms.
JUMMP can combine various aspects of the different mentioned frameworks. Akonadi and Plasma provide good examples for using a plugin architecture on top of an IPC bus in a way like JUMMP uses JMS for the communication. KWin illustrates how core functionality can be replaced by plugins. This can be a role model for additions to JUMMP like search. Compiz provides very good examples on how many plugins can be managed even if they depend on each other or conflict with each other.