After installing the system, you are offered the opportunity to visit the Administration system. If you do not do this, you will find a link to the Admin system on the top right of the main page of the Production system, entitled Administration. This administrative menu item has a relative URL of /admindesktop/{lcd}/dashboard.


You may or may not wish to replicate this link onto your own client website. You may be developing an application which has no website. As you have control of the Routing within a Cliqon system, you will be able to amend your Routes array to provide access to the Admin system in the most appropriate way. In the Production Cliqon system, we have chosen to create the route to the administration component in a non-obvious manner, this allows you to create a more obvious route of your choice, if required.


You might wish to create a route to the AdminController, which read /admin/ or /wp-admin/. You can see the implications of that ability.

Session Control

Overall access control to the system depends on the existence of a Session Cookie containing the user name of a valid user entitled to access parts of the system, beyond the first pages. If you try to access any restricted element of the system without the existence of the Session Cookie, you will be redirected to the Login page.


For the Production system, we only provide one Login page and the production Routes anticipate that there are two levels to the system - visitors and administrators. However when a User has been registered (possibly through their own self-registration or by the action of an Administrator with permissions to create a registered user), they will be awarded an appropriate level of access.

Access Control Level (ACL)

We have chosen to use a numeric system of levels within Cliqon. Whilst we are aware that many systems use as an alphanumeric system which can be either permissive or boolean in effect, our numeric levels, are permissive. Each typical function of Read, Write or Delete has an individual level and thus a Level is displayed as nn:nn:nn (or Read = nn, Write = nn and Delete = nn). On logging into the system, the User, by virtue of their Session User Name is awarded a level, say, 60:70:80 and this level checked for equality and greater (>=) against the level applied to that action within the system.


Let us take several examples to illustrate the system.


A particular instance of Cliqon has one comprehensive or combined Administration system. Our Menu item entitled Structure has a Read Level of 80, whilst our logged in Operator has a Read Level of 60. The Structure menu item will not be displayed, nor will any of its sub-menu components. On the other hand the Utilities Menu item has a Read level of 60, so it will be displayed. However only some its sub-menu items as one or two of them have a Read Level of 70 or greater.


To make matters more secure, not only are the menu items secure but so are all aspects of the functions and facilities they invoke are secure as well. This is a second and subsequent level of control that is or can be configured throughout the system. This prevents deliberate attempts to circumvent the security of the system as well as accidental situations.


For example, the default level needed to write a record with tabletype "string" to table "dbcollection", is 80, thus even if by some means our junior operator had managed to generate a Post that was "clean" and acceptable to the PostForm routine, their attempt to write the record to the database would be thwarted and an error message generated that this action was not permitted.


Another example is that Reading and Writing go down to an even finer level if required. To the extent that individual form fields can be hidden and individual writes can be negated. By default, a Production system only uses ACL at Administration Menu Level. The Level records are not introduced in most of the service, collection and model records, for read, write and delete activities. Also they are not configured by default at any of the formfield and data display column levels.


For a simple presentational website with a couple of people managing the content, the system is complete overkill but it will be reassuring to some clients and their developers that this level of fine control is available.

Default Levels

The ACL system operates with a two digit number between 10 and 99. For own own purposes we have found that jumps in ten units are completely adequate. The list below, represents the levels we instituted for our Busiiness Directory, Classified Advertising system, Jobs Board and Shopping system.


10        * Visitor, not yet logged in

20        * User logged in

30        Business with listing

40        Nominated representative for the Business

50        Agent representing a group of businesses

60        * Operator with lowest level of Administrative access,

70        Manager

80        * Developer, webmaster et cetera

90        * Administrator


The starred levels are present in the dataset for the Production system for the menu items.


The way that a Cliqon system is designed, permits the existence of multiple View directories. A Production system has three "views" subdirectories - /admin, /views and /install. After the system is installed, the /install subdirectory can or will be deleted. In our Cliqon applications we chose to use a combined Admin system which allowed access to all Levels. As a Developer, you might wish to separate the Admin directory into several different content management directories by function - user profiles and their orders, as an example; separated from businesses and their products; separated from an application owners administrative backend. All this is possible and can be configured using the Routes and Controllers.

Authority

There is a slight variation on the previous section which needs a comment. We have found a couple of places where we need to break our own rules, or to put it another way, we have found edge cases. As an example, we identified that only an Administrator or Users for themselves, should be able to change a password. Setting the Write Level on a password at 90, allows Administrator to change a Password but would prevent the User from changing their own Password, so we introduced a flag which accomodates the concept that the record is owned by that User, thus modification is allowed. This will be put to greater use when the current system is expanded to accommodate eCommerce, including shopping carts and sales orders.

JSON Web Tokens and Cross Domain

For the fifteen years that Cliqon has existed, in one form or another, we have always programmed it for the traditional web client to web server relationship. A long time ago we instituted AJAX communication between client and server for the exchange of data and HTML content, so that administrative pages were rarely rewritten in their entirety. In order to explain and understand what comes next, we had to understand and explain how what we have done in the past (and still can do now) is different from what we have provided for, in the future. It would be easy to use jargon words such as we have introduced a REST API or that Cliqon can provide services and data that clients can consume. All of this would be true but how and in what ways are these two situations different.


We have decided to create the following definition that we will use and abide by throughout this documentation. When we talk about an AJAX response by the server to a request from a client, we are expecting that the Server will provide presentation and data. For example Cliqon might first write a table structure via PHP or Javascript to the web client and follow it up by responding with data to populate that table structure. In future, within our Production system, we will identify these requests and routes as starting with the key word in a relative URL of /ajax/; as in - /ajax/{lcd}/getform/dbcollection/string/?=REQUEST. In 80% of the responses within the AJAX controller, the response is JSON but the HTML (or the instructions to make the HTML) is embedded within the JSON. An AJAX response cannot and should not be, cross domain. It does not pass the necessary Call back procedures (though it does abide by the protocol, just that the callback is empty).


Cliqon Version 4 introduces the concept of a RESTful service, which can be cross domain and therefore must and can only be JSONP. Furthermore, we have made the arbitrary decision that API calls can receive whatever they require - that is if only data - that is perfectly acceptable.  In future, within our Production system, we will identify these requests and routes as starting with a key word in a relative URL of /api/; as in - /ajax/{lcd}/getdata/dbitem/news/?=REQUEST and they will be handled by an API controller.


To support the introduction of a RESTful service, we have introduced the concept of a JSON Web Token (JWT) and the API Controller (or PluginController) and any that you as Developers write, will not use Session validation to authorise an aspect of the CRUD process, but will use a JWT that contains the UserID and additional security information generated by the key generation and response process. All Cliqon responses now publish the JWT, most Cliqon Javascript functions and routines know how to handle the Token and send it back. The the client activity invokes the API controller, the Controller will parse the Token and know how to use its important contents (eg, the UserID) to validate the transaction according to the standard Cliqon ACL rules.


However, this explanation above is not the point. A standard Cliqon presentational website does not really need a Restful service. This will happen when Developers start to devise and design applications that run on non web devices that want to access services on the Cliqon server where a PHP orientated Session variable has no meaning. We believe that Cliqon has taken the right road towards the implementation of JSON Web Tokens but we will be pleased to make amendments as the need arises.

Simple JWT by example

There are many different but conformant ways of writing code that publishes and uses JWT. We did not write our own handlers from scratch, we applied what was already available.


REST API's are meant to be stateless. What that means is that each request from a client should include all the information needed to process the request. In other words, if you are writing a REST API in PHP then you should not be using $_SESSION to store data about the client's session. But then how do we remember if a client is logged in or anything else about their state? The only possibility is that the client must be tasked with keeping track of the state. How could this ever be done securely? The client can't be trusted!


Enter JSON web tokens. A JSON web token is a bit of JSON, perhaps something that looks like this:


{

   "user": "alice",

   "email": "test@nospam.com"

}


Of course, we can't just give this to a client and have them give it back to us without some sort of assurance that it hasn't been tampered with. After all, what if they edit the token as follows:


{

   "user": "administrator",

   "email": "test@nospam.com"

}


The solution to this is that JSON web tokens are signed by the server. If the client tampers with the data then the token's signature will no longer match and an error can be raised. The JWT PHP class makes this easy to do. For example, to create a token after the client successfully logs in, the following code could be used:


$token = array();

$token['id'] = $id;

echo F::encode($token, 'secret_server_key');


And then on later API calls the token can be retrieved and verified by this code:


$token = F::decode($_POST['token'], 'secret_server_key');

echo $token->id;


If the token has been tampered with then $token will be empty there will not be an id available. The JWT class makes sure that invalid data is never made available. If the token is tampered with, it will be unusable. The contents of the JWT Class have been incorporated into Framework.Php and the Framework Class.


The token is generated and published, probably by the initial Login routine. It will then be stored on the Client device.


In a Controller

$token = array(); $token['id'] = $_SESSION['UserName'];


When generating the Template

'jwt' => F::encode($token, $this->cfg['site']['secret']),


When creating the Global variables in admscript.tpl

var jwt = "@raw($jwt)"; // This is now essential


Below are two Javascript snippets that show


var urlstr = cfg.opts.url;

aja().method('GET').url(urlstr).cache(false).timeout(10000).type('jsonp')

.data({

       limit: cfg.records.limit,

       offset: cfg.records.offset,

       search: cfg.search,

       orderby: orderby,

       field_identifier: 'field',

       token: jwt

})

.jsonPaddingName('clientRequest')

.jsonPadding('cliqon') // becomes ajajsonp_cliqon

.on('40x', function(response) { error('Page not Found - '+urlstr+':'+response)})

.on('500', function(response) { error('Server Error - '+urlstr+':'+response)})

.on('timeout', function(response){ error('Timeout - '+urlstr+':'+response)})

.on('200', function(response) {

       if(typeof response == 'object')

       {

               // Test NotOK - value already exists

               var match = /NotOk/.test(response.flag);

               if(!match == true) {


                       cfg.records.offset = response.offset;

                       cfg.records.limit = response.limit;

                       cfg.records.total = response.total;

                       cfg.dt.$data.rows = response.rows;

                       pagerText();


               } else { error('Ajax function returned error NotOk - '+urlstr+':'+JSON.stringify(response)); };

       } else { error('Response was not JSON object - '+urlstr+':'+response.msg); };

}).go();


OR


var ppSubmit = function(e)

{        

       var frmData = ppGetData();

       frmData.set('token', jwt);

       var urlstr = '/plugin/'+jlcd+'/updateuser/dbuser';

       $.ajax({

               url: urlstr, data: frmData,

               cache: false, contentType: false, processData: false,

               type: 'POST', async: false, timeout: 25000,

               success: function(response, statusText, xhr) {

                       // first argument to the success callback is the json data object returned by the server

                       if(typeof response == 'object') {

                               pcfg.spinner.stop();

                               var match = /NotOk/.test(response.flag);

                               if(match == true) {

                                       Cliq.error('Ajax function returned error NotOk - '+JSON.stringify(response.msg))

                               };                          

                       } else {

                               Cliq.error('Response was not JSON object - '+JSON.stringify(response))

                       };        

               },

               error: function(xhr, status, text) {

                       pcfg.spinner.stop();

                       var response = $.parseJSON(xhr.responseText);

                       Cliq.error(JSON.stringify(response.msg));

                       return false;

               }

       });  

}


And in the PHP Controller


// Introduce JWT security here

$token = F::decode($rq['token'], $cfg['site']['secret'], false);

if($token['id'] == "") {

       throw new Exception("Not a valid token!");

} else if(!Q::cUname($token['id'])) { // $_SESSION['UserName'] or User = False

       throw new Exception("Not a valid user!");

};


The Token is matched against a valid User.


For a more comprehensive implementation of Token management, you will need to implement the Set and Get Tokens that can be found in Apiservices.Php. The above examples were devised to test the simple mechanism.






Created with the Personal Edition of HelpNDoc: Write eBooks for the Kindle