# Introduction to SPOPS

## Introduction

SPOPS -- or Simple Perl Object Persistence with Security -- allows you to easily define the structure of an object and to save, fetch or remove it any time thereafter. It is intended for SQL databases (using the DBI), but you can adapt it to use any storage mechanism for accomplishing these tasks. SPOPS also implements security on a per-object basis (authentication and authorization).

The goals of this module are:

- Make it easy to define the parameters of an object

- Make it easy to do common operations (fetch, save, remove)

- Get rid of as much SQL as possible, but do not impose a cumbersome framework on the developer

- Make applications easily portable from one database to another

- Include flexibility to allow extensions

- Allow the developer to simply issue SQL statements and work with normal datasets

- Create a transparent security framework for data that is independent of the data source.

# Persistence

## Classes

SPOPS (Simple Perl Object Persistence with Security) provides a framework to make your application objects persistent (meaning, you can store them somewhere, e.g., in a relational database), and to control access to them (the usual user/group access rights stuff).

You will configure SPOPS by means of configuration files, and SPOPS will create the necessary classes and objects for your application on the fly. You can of course have your own code implement additional object behavior -- extending the default SPOPS object behavior with your methods. However, if SPOPS shall know about your classes and objects, you will have to tell it -- by configuring it.

The typical class hierarchy for an SPOPS object looks like this:

         --------------------------
        |SPOPS                     |
         --------------------------
                    ^
                    |
         --------------------------
        |SPOPS::MyStorageTechnology|
         --------------------------
                    ^
                    |
         --------------------------
        |SPOPS::MyApplicationClass |
         --------------------------

SPOPS

  Abstract base class, provides persistency and security framework (fetch, save, remove)

SPOPS::MyStorageTechnology

  Concrete base class, provides technical implementation of framework for a particular storage technology (e.g., Filesystem, RDBMS, LDAP, ... )

  Example: SPOPS::DBI, SPOPS::GDBM, SPOPS::Imperia ...

SPOPS::MyApplicationClass

  User class, provides semantic implementation of framework (configuration of parent class, e.g., database connection strings, field mappings, ... )

  Example: MyApplication::User, MyApplication::Document, ...

The individual objects in a package should not care how the objects are being stored, they should just know that when they call fetch() with a unique ID that the object magically appears. Similarly, all the object should know is that it calls save() on itself and can reappear at any later date with the proper invocation.

## SPOPS Object States

Basically, each SPOPS object is always in one of two states:

- Runtime State

- Persistency State

In Runtime State, the object representation is based on a hash of attributes. The object gets notified about any changes to it through the `tie(3)` mechanism.

In Persistency State, the object exists in some persistent form, that is, it is stored in a database, or written out to a file.

You can control what happens to the object when it gets written to its persistent form, or when it is deleted, or fetched from its storage form, by implementing a simple API: `fetch(),` `save(),` `remove().`

         -------------         save, remove         -----------------
        |Runtime State|     ------------------->   |Persistency State|
         -------------      <------------------     -----------------
                                  fetch

# Security

## Overview of security

By adding this module into the hierarchy for an SPOPS object class, you implement a transparent per-object security system. This security system relies on a few things being implemented:

- A spops class implementing users
- A spops class implementing groups
- A spops class implementing security objects

Security is implemented with a number of methods that are called within the SPOPS implementation module. For instance, every time you call `fetch()` on an object, the system first determines whether you have rights to do so. Similar callbacks are located in `save()` and `remove()`. If you do not either define the method in your SPOPS implementation or use this module, the action will always be allowed.

We use a Unix-style permission scheme, separating the scope into: USER, GROUP and WORLD from most- to least-specific. (This is abbreviated as U/G/W.) When we check permissions, we check whether a security level is defined for the most-specific item first, then work our way up to the least specific.

Even though we use the U/G/W scheme from Unix, we are not constrained by its history. There is no strict 'ownership' assigned to an object as there is to a Unix file. Instead, an object can have assigned to it permissions from any number of users, and any number of groups.

There are three settings for any object combined with a specific scope:

     NONE:  The scope is barred from even seeing the object.
     READ:  The scope can read the object but not save it.
     WRITE: The scope can read, write and delete the object.

(To be explicit: WRITE permission implies READ permission as well; if a scope has WRITE permission for an object, it can do anything with it, including remove it.)

## Security rules

With security, there are some important assumptions. These rules are laid out here.

- The most specific security wins. This means that you might have set permissions on an object to be SEC_LEVEL_WRITE for SEC_LEVEL_WORLD, but if the user who is logged in has SEC_LEVEL_NONE, permission will be denied.
- All objects must have a WORLD permission. Configuration for your SPOPS object must include the initial_security hash. The only required field is 'WORLD', which defines the default WORLD permission for newly-created objects. If you do not include this, the system will automatically set the WORLD permission to SEC_LEVEL_NONE, which is probably not what you want.

For instance, look at an object that represents a news notice posted:

     Object Class: MyApp::News
     Object ID:    1625

     ------------------------------------------------
     | SCOPE | SCOPE_ID |  NONE  |  READ  |  WRITE  |
     ------------------------------------------------
     | USER  | 71827    |        |   X    |         |
     | USER  | 6351     |   X    |        |         |
     | USER  | 9182     |        |        |    X    |
     | GROUP | 762      |        |   X    |         |
     | GROUP | 938      |        |        |    X    |
     | WORLD |          |        |   X    |         |
     ------------------------------------------------

From this, we can say:

- User 6351 can never view this notice. Even though the user might be a part of a group that can; even though WORLD has READ permission. Since the user is explicitly forbidden from viewing the notice, nothing else matters.
- If a different User (say, 21092) who belongs to both Group 762 and Group 938 tries to determine permission for this object, that User will have WRITE permission since the system returns the highest permission granted by all Group memberships.
- Any user who is not specified here and who does not belong to either Group 762 or Group 938 will get READ permission to the object, reverting to the permission for the scope WORLD.