/**
* \file
* This is the generic application router....
* Here RBAC can take place as every resource is always requested
* via an HTTP request.
*
* \author Georg Hopp
*
* \copyright
* Copyright © 2013 Georg Hopp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
// for strchr and others.
#include
// for size_t
#include
// for dlopen, dlsym
#include
// for toupper
#include
#include "router.h"
#include "hash.h"
#include "session.h"
#include "http/request.h"
#include "http/response.h"
#include "application/application.h"
#include "utils/memory.h"
#include "commons.h"
#define COMMAND_LEN 128
HttpResponse
routerRoute(
Router this,
HttpRequest request,
Session sess)
{
char functionName[COMMAND_LEN + this->nprefix * 10];
Hash args = NULL;
fptr_routable function;
char * tmp;
char * command;
size_t ncommand;
char * response_data;
HttpResponse response;
if ('/' != request->uri[0]) {
/*
* we only support absolute paths within our
* application
*/
return NULL;
}
command = &(request->uri[1]);
command[0] = toupper(command[0]);
/*
* find end of command
*/
tmp = strchr(command, '/');
if (NULL == tmp) {
ncommand = strlen(command);
} else {
ncommand = tmp - command;
}
memcpy(functionName, this->prefix, this->nprefix);
memcpy(&(functionName[this->nprefix]),
command, MIN(COMMAND_LEN, ncommand));
/**
* \todo
* now get all arguments if we have some
*/
/*
* following the crud pattern we map the first part
* of the uri and the request method to according
* function names.
*/
switch (request->method_id) {
case HTTP_GET:
args = new(Hash);
strcpy(&(functionName[this->nprefix + ncommand]), "Read");
break;
case HTTP_POST:
args = request->post;
strcpy(&(functionName[this->nprefix + ncommand]), "Create");
break;
case HTTP_PUT:
strcpy(&(functionName[this->nprefix + ncommand]), "Update");
break;
case HTTP_DELETE:
strcpy(&(functionName[this->nprefix + ncommand]), "Delete");
break;
default:
/* other methods are not subject of REST */
return NULL;
}
/*
* \todo for the moment I don't cache the found symbol...
* I don't even check if there was an error...the only thing
* I do is checking a NULL symbol and in that case don't
* handle the request here.
*/
dlerror();
function = dlsym(this->handle, functionName);
/**
* \todo somewhere here or above access control have to take place
* Default policy should be deny, anyway, there are a few resource
* that should be accessible even when not logged in...the are at
* least most of the assets as well as functions like version or
* sessinfo and in fact currentuse to have a way to find out that
* one is not logged in.
* In general a deny will be handled by storing an error message in
* some stash and then trigger a redirect to the login page.
* To be really rbac it seems neccessary to me to create a user
* "not logged in" and assign him the exceptions to the default
* deny policy.
* For the moment I assume that if there is no resource for the
* URL in the application it must be an asset and just return NULL
* indication that we still have no response for the request.
* Another thought... resources will be created dynamically by
* creating tasks or users or anything.
* Each of these resources may have options to admin them. This means
* most of the time to be able to modify them but additionally the
* creater of the resource might need the right to modify the
* rbac rules that apply to that resource.
* So, if I keep the real resources and their rbac configuration
* separated as planned it might be neccessary to give the creater
* of a resource the ability to modify both.
*
* So lets assume user georg creates a task that might be identified
* by /task/uuid(task). Then additionally an rbac resource will be
* created identified by /rbac_resource/uuid(/task/uuid(task)).
* User georg will have all rights on both resources.
* This means that rbac resources are resources by their own but how
* to control the access to these, I can't build another rbac resource
* and another and and and... so I think it is neccessary that every
* resource as it is has to hold their access in itself.
* The creating user will gain access to all REST operations as well
* as the right to change access control (which again are REST operations
* on these.
*
* Sidenote: I use a slightly differen naming than the ansi spec uses
* I the term resource for object and action for operation.
*
* So most resources end up with the following set of possible actions:
* - create: (well obviously this is only useful for list resources
* eg. the tasklist of a new project)
* - read: be able to display the resource...
* (again there is a special thing with lists here. This
* only gives the right to see the list at all. When
* generating the list the access rights on each entry
* has to be checked and if there is no read right for it
* it should not be included in the list.)
* - update: be able to update a resource.
* (this makes no sense for list resources as the change when
* their members change)
* - delete: be able to remove a resource.
* (on list resources this should only be allowed if the list
* is empty, this is the only consistent behaviour I can think
* of because you can't always assume that by removing a
* list ii's associated members should also be removed)
* - rbac_read:
* - rbac_update:
*
* Well, rbac assignes only roles to resources... in that case, how can I
* achieve per user rights for specific resources... one way would be
* to give every user its own role, which makes the whole concept kind
* of useless.
*
* Then I could allow everyone to create new roles on demand. Then
* a user would create a role that allows others to view the resource
* and then add user to this role. This role creation could be done
* automatically and in the UI the user simply only adds the users
* that should have access to the specific action.
* On the other hand the user might associate an action on the resource
* to an existing role.
* thus giving, for example, all team members the right to use the
* according action. Again in the UI this would be a simple select
* from a list.
* Still it seems neccessary to have a suer_private role where only
* this one user is in and that has full access to all resource actions
* of each resource the user is creating...and if there is such a thing
* no new roles will be created when allowing others to take actions
* on specific resources...simply add the action to the private role of
* the other user.
* This private roles can be almost automatic.
* (created when user is created, removed when he is removed, etc. etc)
* Regarding the session...I hink it ok to use our sessions to store
* The resulting access rights defined by the roles the user is in.
* On the other hand...if we store them stere no immediate feedback is
* possible when one of the roles have been changed....well, maybe
* there is...each existing session for users that are associated with
* the changed role have to be updated. That is in any case better
* than calculating all the access right on every reqeust.
* So, what we have in place right now are users and sessions. Both
* can be extended to the needs for rbac.
* What we still need is a definition of resources and actions that
* build up a permission and roles in it self that will associate user
* with permissions.
*/
if (NULL == function) {
/**
* nothing there to handle the request ... so leave it to the
* caller...
*/
char * error;
if (NULL != (error = dlerror())) {
/**
* \todo add logging...maybe.
*/
}
return NULL;
}
/*
* function has to allocate the memory for reponse_date by using
* memMalloc.
*/
response_data = function(this->application, sess, args);
switch (request->method_id) {
case HTTP_GET:
delete(args);
break;
case HTTP_POST:
case HTTP_PUT:
case HTTP_DELETE:
default:
/* other methods are not subject of REST */
break;
}
response = httpResponseJson(response_data, strlen(response_data));
MEM_FREE(response_data);
return response;
}
// vim: set ts=4 sw=4: