Architecture

The architecture of ringo is shown below.

Schema of a modul.

Ringo is a Pyramid based application which can be extended to build your own applications. This is a layered architecture where Pyramid brings in the basic functionality like session handling and handling requests and responses in web applications. Ringo sits on top of Pyramid and provides commonly used functionality often used in modern web applications. See Features for an overview.

Note

Ringo is also a standalone application. You do not need to build another application on top of ringo to get something working to get an impression of ringo or start developing. Ringo is under steady development. This is a benefit if you use Ringo as base for your application. Your application will get bug fixes, new features or improved functionality in most cases by simply updating Ringo to a new version.

Ringo itself uses some external libraries to provide some of its functionality. E.g the formbar library is used to build all forms and do validation. The access to the database is done with the ORM SQLAlchemy.

A ringo based application is another pyramid based application which basically extends ringo. See Exending An Existing Pyramid Application for more details on how this is done.

All this magic is already done in a pyramid scaffold which comes with ringo. Using this scaffold will create an application which uses ringo functionality by simply importing it at the right places and take care the basic configuration is done in the proper way. See General for information on how to create an application using this scaffold.

Filesystem

Ringo is organised in the following file layout:

.
├── alembic
├── docs
└── ringo
   ├── lib
   ├── locale
   ├── model
   ├── scaffolds
   ├── scripts
   ├── static
   │   ├── bootstrap
   │   ├── css
   │   ├── images
   │   └── js
   ├── templates
   │   ├── auth
   │   ├── default
   │   ├── internal
   │   ├── mails
   │   └── users
   ├── tests
   ├── teens
   └── views
       ├── forms
       └── tables
alembic
Migration scripts for the database.
docs
Documentation of ringo. This documentation.
ringo
The ringo application.
ringo/lib
Helper libraries. lenderers, validators, security related functions.
ringo/locale
i18n Internationalisation
ringo/model
Models for users, usergroups, modules, roles etc.
ringo/scaffolds
A scaffolds includes the boilerplate code to create a ringo based application.
ringo/scripts
Administration commands and scripts.
ringo/static
Static files like images or CSS and JS scripts.
ringo/templates
Templates for various parts of the application. Templates in the internal folder are used internally by customs form or overview renderers. The default folder has the templates for default CRUD actions and confirmation dialogs.
ringo/tests
Unit tests and behaviour driven tests. See tests for more details.
ringo/tweens
Middleware like code. Used to modify headers in response objects.
ringo/views
Views with the business logic for the application.
ringo/views/forms
Configuration of forms for each modul.
ringo/views/tables
Configuration of overview tables for each modul.

Database

Below you see the basic schema of the ringo database. The schema only lists some central tables which help to understand how the things are wired together in ringo. The table colored orange example table in the top left is an example for any other table which will store items of a modul. Every item of an item has a reference to the module it belogs to. Further the current example has a reference to a user and a usergroup which defines the ownership [1] per item is is important for the authorisation.

Basic database model.

Let us begin with the modules table. This table will store information on all available modules in the system. It basically stores the configuration per modul. As described in the Overviews section each modul has (module_actions) which are stored in the actions table. The NM-table nm_actions_roles define which roles are allowed to use the actions in the module. See permissionsystem for more information on how the Authorisation is implemented in ringo.

The users table stores all the users in ringo. The users table only holds minimal data which is required for authentification and authorisation. Additional information like the name, email etc. is stored in the profiles table. Every user has a profile.

Users can be organised in groups using the nm_user_groups table. All usergroups are stored in the usergroups table. Roles can be assigned to usergroups and users. This is done with the NM-table nm_user_roles and nm_usergroup_roles.

The table user_settings and password_reset_request are helper tables to save user settings like saved search queries or store the tokens to trigger the password reset.

[1]The ownership feature can be added by using the Owned mixin.

Modules

The term “Module” is central and often used in ringo. Therefore it is important to understand what a module is. This section tries to explain that.

Schema of a module.

You can think of a module in the way that it provides the infrastructure to work with a certain type of data in a web application. Where certain type of data means users, files, movies etc. rather than integers or datevalues. Lets call them items from now on.

A module will have a model for the items you want to work with. It will include views to handle incoming request and generating the proper responses for users. Further it has templates which define how the pages in the application will look like. Finally there are configuration files to define how the forms and overview tables will look like.

Ringo already come with many modules. One module per item. There is a module for the user management, a module for appointment and so on. There is also a module to handle the modules itself. So we can say in general: ringos functionality is the sum of all modules functionality. Ringo or a ringo based application can be extended by adding new modules. Fortunately you will not need to create this infrastructure for you own. See Add a new modul to your application for more information.

Each module has a common set of configuration options which can be done directly in the web interface. See the modules entry in the administion menu for more information.

Modules provide actions which can be used to manipulate the item of a module. Ringo provides some basic CRUD [2] actions which are available on default for every module.

  • Create: Create new items.
  • Read: Show the item in detail in readonly mode.
  • Update: Edit items of the module.
  • Delete: Deleting items.
  • List: Listing all items of the module. This is slightly different to the action to read a single item.
  • Import (CSV, JSON)
  • Export (CSV, JSON)

Every time you want to do something different to your items you will likely want to implement another action for the module. See Adding a single custom actions to a module for more details how to add actions to a module.

[2]CRUD means: Create, Read, Update, Delete

Extensions

Extensions are external pluggable modules and a option to add generic functionallity to the application in a dynamic way.

In contrast to modules, extensions are generic and should not implement application specific stuff. Extensions should be used to create pluggable modules with generic functionallity which can be usefull for all kind of applications. An example might be an appointment extensions which allows the user to extend the application with an appointment feature.

Extension can be Registered to an application by adding the extentions in the extensions list located in the modul/__init__.py file. As soon as the application is started the extension will be registered. To unregister a extension simply remove the extension from the extentions list.

Note

For extensions which modifies the datamodel of the application a seperate model migration is needed on registration and unregistration of the extension. (See alembic and ringo-admin documentation).

For more information on how to create and register extensions please refer to the Development part of the documentation.

Mixins

Mixins can be used to add certain functionality to the items of a module. Mixins are used in multiple inheritance. The mixin ensures that the item will have all needed fields in the database and provides the proper interface to use the added functionality. Example:

class Comment(BaseItem, Nested, Meta, Owned, Base):
    __tablename__ = 'comments'
    _modul_id = 99
    id = sa.Column(sa.Integer, primary_key=True)
    comment = sa.Column('comment', sa.Text)

    ...

The comment class in the example only defines the two fields id and comment. But as it inherits from Nested, Meta and Owned it also will have date fields with the creation and date of the last update, references to the user and group which ownes the Comment. Further the ‘Nested’ mixin will ensure the comments can reference each other to be able to build a hierarchy structure (e.g Threads in the example of the comments).

Important

As most of the mixins will add additional tables and database fields to your item it is needed to migrate your database to the new model. See alembic_migration section for more information.

Meta

class ringo.model.mixins.Meta

Mixin to add a created and a updated datefield to items. The updated datefield will be updated on every update of the item with the datetime of the update. Date will be saved in UTC

Owned

class ringo.model.mixins.Owned

Mixin to add references to a user and a usergroup. This references are used to build some kind of ownership of the item. The ownership is used from the permission system.

It is posible to configure inhertinace of the owner and group from a given parent element. This information is used only while creating new instances of the modul. If configured, the default group and owner information will be overwritten. This is done at the very end of the creation process. See ‘’save’’ method of the BaseItem. You can configure the inhertiance by setting the name of the relation to the parent item in the ‘’_inherit_gid’’ and ‘’_inherit_uid’’ class variable.

Nested

class ringo.model.mixins.Nested

Mixin to make nested (self-reference) Items possible. Each item can have a parent item and many children. The class will add two relation attribute to the inheriting class. The parent item is available under the parent attribute. The children items are available under the children attribute.

Logged

class ringo.model.mixins.Logged

Mixin to add logging functionallity to a modul. Adding this Mixin the item of a modul will have a “logs” relationship containing all the log entries for this item. Log entries can be created automatically by the system or may be created manual by the user. Manual log entries. Needs to be configured (Permissions)

Versioned

class ringo.model.mixins.Versioned

Mixin to add version functionallity to a modul. This mixin is used to store different “versions” of an item. On each update of the item, the serialized values of the item will be saved in the version table. Item modul will have a “versions” relationship containing all versions of the values for this item.

StateMixin

class ringo.model.mixins.StateMixin

Mixin to add one or more Statemachines to an item. The statemachines are stored in a internal ‘_statemachines’ dictionary. The current state is stored as integer value per item. This field must be created manually. The name of the field which stores the value for the current state must be the keyname of the ‘_statemachines’ dictionary.

Example Mixin usage:

class FooMixin(StateMixin):
    # Mixin inherited from the StateMixin to add the Foobar
    # state machine

    # Attach the statemachines to an internal dictionary
    _statemachines = {'foo_state_id': FooStatemachine}

    # Configue a field in the model which saves the current
    # state per state machine
    foo_state_id = sa.Column(sa.Integer, default=1)

    # Optional. Create a property to access the statemachine
    # like an attribute. This gets usefull if you want to access
    # the state in overview lists.
    @property
    def foo_state(self):
        state = self.get_statemachine('foo_state_id')
        return state.get_state()

Commented

class ringo.model.mixins.Commented

Mixin to add comment functionallity to a modul. Adding this Mixin the item of a modul will have a “comments” relationship containing all the comment entries for this item.

Tagged

class ringo.model.mixins.Tagged

Mixin to add tag (keyword) functionallity to a modul. Adding this Mixin the item of a modul will have a “tags” relationship containing all the tag entries for this item.

Todo

class ringo.model.mixins.Todo

Mixin to add todo functionallity to a modul. Adding this Mixin the item of a modul will have a “todo” relationship containing all the todo entries for this item.

Printable

class ringo.model.mixins.Printable

Event Handlers

Each modul can implement one of the following event handlers to realize automatic modifications of the items:

  • create_event_handler(request, item, **kwargs)
  • update_event_handler(request, item, **kwargs)
  • delete_event_handler(request, item, **kwargs)

The functions will be called for all base classes of the item automatically in the base controller if the specific view function for the event (update, create, delete) is excecuted.

Some of the Mixin classes do already have some predefined event_handlers configured.

Security

Security is an important aspect of ringo. This chapter will describe the permission system and explains how ringo handle common security threats.

Permission System

The permission system addresses two basic questions:

  1. Who is allowed to access some item in general and
  2. What is allowed for the user to access, in case he is generally allowed to access the item.

To answer these two questions the permission system of Ringo is a combination of concepts of the permission system known from the Unix file system and a roles based permission system.

Activity diagram of the permission check.

The Unix file system part answers the first question: Who is allowed? Therefor every item in the system inherited from the Owned stores information to which owner and which group it belongs to. Only the owner, members of the group or users with an administrational role are granted access to the item in general.

After the permission to access the item in general is allowed, the role bases system answers the second question: What is allowed. The permission system will now check which roles the users have and which actions are allowed for these roles.

There are currently two ways a user can be equiped with permissions:

1. If the user is the owner of the item, or is member of the items group, then all permissions of the users roles will be applied.

2. If the user is member of the items group, then the permissins of the group will additionally be applied.

Note

Currently there is no anonymous access to the item. See Issue61 in the ringo bugtracker. A workaround might be to setup a user group with all users of the system and assing the needed roles to it. Then set this group as the item group.

See Authorisation for more details on this.

Authentification

Authentication is done once in the login process. If the user has logged in successful an auth cookie is saved. From then on the user object is loaded from the database on every request with the roles and groups attached to the user. This user object is used later for the Authorisation. If the user is not logged in the user object is empty.

The authentification has a default timeout of 30min. The timeout will be reset after every new request of the user. The timeout can be configured in the application configuration bei setting the ‘auth.timeout’ config variable.

Authorisation

The permission system in Ringo uses the Pyramid Pyramid Authorisation and Authenfication API

Authorisation is done on every request. The authorisation will check if the user is allowed to access the requested resource.

A resource is an url or an item which is accessed by calling the url in your application. In all cases this resource is build from a resource factory for every request. Every resource will have an ACL which determines if the user of the current request (See Authentification) is allowed to access the resource.

Ringo’s part in the authorisation process is to build the ACL. This ACL is then used by the Pyramid security API. Therefor ringo implements helper functions to build ACL lists which model the ringo permission system.

See Adding Authorization tutorial for more information how things work in general under the hood.

See Security for documentation on helper functions used to build the ACL.

Security measurements

Ringo has protection against common threads of webapplication included.

CSRF-Protection

To protect against CSRF attacks ringo follows the recommodation of OWASP and adds a synchroniser token to each form, which will be sent and checked on each POST request. The token will be unique on every request. GET requests in ringo are not protected as GET functions in ringo should be idempotent and does not trigger expensive opertaions. Following this simple philosophie on GET requests will make any further CSRF protection obsolete.

XSS-Protection

Ringo will add the following headers to protect the application against XSS attacks.

  • ‘X-XSS-Protection’: ‘1; mode=block’,
  • ‘X-Content-Type-Options’: ‘nosniff’

Further ringo provides an option to enable a contect CSP for further protection. The CSP is disabled on default but can be enabled in the application Headers configuration.

Clickjacking-Protection

DOS-Protection

DOS protection is not handled by ringo. Protection against DOS-attacks should be handled by the Reverse Proxy or Firewall.

States and Workflows

Ringo offers the option to equip items of the modules with a one or more state machines to model statefull workflows.

An classical example for a statefull workflow is a process where an item can be in an draft, review and published state.

The statemachine offers features like

  • Restrictions on access
  • Conditional transitions
  • Handlers.

Depending on the state the state machine can be configured to restrict access to the item for certain users. Further you can define conditions which must be true before the state can switch into another. Finally you can write certain handlers which are called right after the state has changed.

A state machine can be attached to the items using a StateMixin which organises the state machines an provides a unique interface.

For detailed descriptione of the involved classes see API documentation of Statemachine