/** * \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: