

    SSO SERVER DESIGN AND CODING


Author: Petr Gladkikh <pgladkikh@parallels.com>

Note:
    Additional documentation on the server can be found in source code and
    in WIKI http://wiki.plesk.ru/wiki/SSO-SAML
    

    ---
    DESIGN


    Control flow

Server's entry point is sso_main.php. This is only file that should handle all
requests to the server. So other source files should be unavailable to external users.
Basic request handling procedure is:

1. Construct handler service. Handler is object that accepts HTTP request object as input
and returns HTTP response object as result.
2. Construct request object.
3. Feed request to service instance and get reponse object.
4. Render HTTP response according to response object.

URL mapping is implemented via 'composite' pattern see base_DispatchService.
All error conditions are converted to exceptions. Unhandled exceptions are automatically
logged at top level. Exception's error code is used as HTTP status code.

Processing logic is defined by a base_Service instance. Services (resource handlers) can be combined.
Currently services are combined as follows:
base_ServiceChain
    authenticator_1
    ...
    authenticator_n
    base_Authorizator
        base_AccessPolicyList
            access_policy_1
            ...
            access_policy_k
    base_DispatchService
        path_regexp_1 => handler_1
        ...
        path_regexp_m => handler_m

Service chain asks each handler in list and if handler returns non null response then this
response is returned to client and subsequent handlers are not used.

So, any request is first authenticated, then access policy is checked and then it is processed
by one of handlers in dispatch service.
Any authenticator may return own response for example it can be a login page; in this case
rest of processing chain is not used. If access policy denies current request then processing
is also stopped and 'Access forbidden' HTTP status is returned. If dispatch service founds
no appropriate handler then 'Not found' HTTP response is returned. Of course if processing
reaches one of application handlers then said handler may return any of these responses too
if it is necessary for application logic.

If server runs in debug mode then debugging interfaces are added to resulting handler service.
Thus in production mode debugging handlers are not even present in system.

Of course other layouts handling chains are possible (e.g. ones that make own auth/authz/process
handlers for every resource). But the described above scheme is currently most reasonable.


    Database

Database access is performed via simple ORM layer. This layer is not very effective but
has sufficient performance. If necessary some caching and shortcuts can be added
to improve performance. 

All DB interactions at application layer are performed via a db_IStorage instance.
This interface provides CRUD operations on persistent objects and has a facility to
perform custom queries. Persistent objects are decorated with db_Persistent base class 
that adds default implementation for 'save' method. 
db_AbstractObject class provides uniform way to describe object-relational mapping.
Thus storage layer is not orthogonal.

Object-relational mapping could be made orthogonal with some kind of fields and annotations.
But this is not implemented due to performance considerations (parsing annotations every
time would be much slower - perhaps some caching is needed for this). 'save' method is not
necessarily should be attached to object but it turned out to be more convenient (otherwise one
need to have reference to db_IStorage to save object).

Persistent objects may have post-load, post-save and pre-delete hooks. If persystent object 
defines one of methods 'load', 'save' or 'delete' then this method will be called within
appropriate storage operation.       

Currently all persistent objects are put into 'domain' package but this is just for convenience. 

Generally life-cycle of an object looks like this:
 $db = ((db_IStorage instance))
 $className = 'domain_SomeClass';
 // Create 
 $o = $db->create($className);
 $o->someProperty = 'Hi there!';
 $o->save();
 $identifier = $o->id;
 // Read/update
 $o = $db->queryForObject($className, $identifier);
 $o->someProperty = 'Bye!';
 $o->somethingCount += 1;
 $o->save();
 // Delete
 $o->delete();
     
Databse structure is updated automatically by post-installation script. Implementation
details of DB creation and schema update are described in db/versioning/readme.txt


    Conversation handling

Cookies are used only for session tracking.     
No conversation state is stored in persistent storage (DB or filesystem). This ensures that service
is easily scalable. Only exception to this rule - conversation state in single logout protocol
(SSOut, see below).

If conversation spans several requests then required state is passed with request and is
returned by answering party intact (within code this state is called "relay state"). 
To prevent accidental or malicious modifications of that state it is always signed with private key.
Single logout protocol may require requesting several parties before returning response
to original request so if conversation satate is passed around then to be able return response
to original request we need every party to behave well. This gets highly unreliable.
Thus conversation state is stored persistently. However if one considers reliability
to be sufficient then SSOut can be made "sateless" also.     


    Plugins 

Server's functionality can be extended with add-on modules. Support for such modules 
is implemented with lib_ExtensionLoader. See file ext/extensions-readme.txt 
for more detailed description. Note that parsing of PHP comments slows down initialization
so use plugins sparingly or automatically generate some PHP file that would act as cache
of plugin parsing results.


    SAML Protocol

SAML Protocol that is used in the server is described in Wiki
    http://wiki.plesk.ru/wiki/SSO-SAML_Protocol

Some standard-compatible extensions are used int his protocol. However currently only
standard compatibility is not sufficient for third party application to work with SSO server.
See also http://wiki.plesk.ru/wiki/SSO-SAML_Protocol_comments

    
    ---
    SECURITY ISSUES


There are several levels at which access control should be considered: 
    1. Secure server setup (proper access rights to server's files in OS)
    2. HTTP resource level - restricting access to HTTP resources.
    3. SAML messages level - ensuring that SSO (SAML ans UI) protocol and implementation is secure.
    4. Resilience to brute force and DoS attacks.

Access to HTTP resources is restricted by HTTP requests processing. SAML is protected 
by signing messages with sender's keypair. Content of SAML messages is protected
by transport (HTTPS). However given that SAML messages are signed (and therefore
integrity protected) disclosing SAML messages content is not dangerous (unless
you consider entities' identifiers sensitive information).

Currently SSO server is subject to DoS and brute-force password attacks. This issue 
needs to be corrected. Some of possible attack vectors:
    1. Depleting DB resources by registering too many objects (primarily SP registrations 
and FI accounts)
    2. DoS attacks with incorrectly signed SAML messages (signature verification takes 
significant CPU resources)
    3. Password brute-force and DoS attacks: currently number of failed credenital checking
attempts is not limited.


    ---
    CODING


    Restrictions

 1. NO output (echo, print, print_r, etc) is allowed within code except outputs in top level code
(base/main.php file). One possible exception would be 'passthru' but it is not required in any
way within SSO server's domain logic. Even if you do need it, put it in top level handling code
and pass appropriate reference to a stream in base_Response object.
 2. NO global variables should be used (including but not limited to _COOKIE, _SERVER, _GET and like).
 3. NO 'exit' or 'die' calls. (Only place where it is used is termination after logging of
fatal error). Use exceptions to indicate problem situations.
 4. As a consequence of above one do not need and should not mess with output buffering
(ob_* functions). Only place where it is needed is top level handling code.

    Classes

Classes are organized in packages. A source directory corresponds to a package.
Class C in package a/b should be named a_b_C and it's declaration is put into src/a/b/C.php file.
Class definitions are loaded via php __autoload function. No explicit require/include
functions are needed, except in cases of referring to external code. Source file name
with class definition is automatically generated from class name. Note that this makes class
names case sensitive (as file with class definition may not be found (and not be loaded
automatically) if class name case differs from one file name).

    Domain objects

Domain objects are objects that are persistently stored in database. These objects represent
essential state of SSO server.

1. If a domain object needs some initialisation then declare static public method 'create(db_IStorage)'
1.1 If domain class has static function create() then use this function instead of
initializing object manually.
2. Generally, if you need to construct or load (query) a domain object of type domain_C, consider
declaring static public function for that. E.g. domain_C::loadByAttribute(db_IStorage $db, $eyeColor)
3. Note that domain objects can have create, delete and update hooks. Use them to update or
load dependent objects if necessary.

    How to add a resource handler

Given layout described in "Control flow"  following things should be done:
1. Create your own handler class by deriving it from base_Service or base_ServiceBase (latter
option offers embedded HTTP method dispatch)
2. Add handler to a dispatch service. Look for examples in functions make_sso_aspect
or make_ui_aspect. "SSO aspect" is for core SSO functionality; "UI aspect" for administration
or debugging (it is disabled by default in production configuration) as it currently
offers almost no security.
3. (optional) Add new authenticator (if some new authentication procedure is required)
4. Add appropriate acces policy to autorizator.


    ---
    TESTING


Every class can (and should) have respective unit test. If class being tested is a_b_Class then
respective test case should be named test_a_b_Class (and hence be put in test/a/b/Class.php).
See 'test' source directory for examples. Directory test/testutils is for auxiliary
code that is used for tests implementation.

If you create new package then you need to add (directly or indirectly) new test
suite to test_Suite. You may use test suite derived from test_testutil_AutoSuite to add all
testcases in current directory (see for example test_domain_Suite or test_db_Suite).

Unit tests can be performed with e.g. these commands
> cd $SOURCES_ROOT/test 
> php test_all.php plain-reporter

It is recommended to ensure that all unit-tests pass before every commit into source repository.


    ---
    REMAINING TASKS


For remaining tasks, desirable refactorings and improvements please see TODO.txt files in 
sources tree and 'TODO' remarks in the code. These remarks are marked with importance level
and functional area so you can decide if it is worth working on.
Example: 'TODO (security, high)' - high priority task related to server security.

See also bugzilla items in http://bugs.plesk.ru: Product=OpenFusion, Component=SSO


    Major desired features
    
1. Password recovery functionality - User email is stored along with FI but it is not used.
Server may send temporary link for resetting password to user via provided email.

2. Customization of server's UI look and feel. Current UI provided by SSO has default look that can
not be customized to match application's UI - this may be disturbing for some use-cases and thus 
custom rendering is always used.

3. LDAP authentication plugin. This feature allows to discard proxied authentication part of
protocol and make intergation cleaner.

4. Use external (or pluggable) directory to store FI usernames and passwords. 
   