Browse Source

Added a possibility of external access to session interface

Added a option to use non-cookies based session ID storage

Tests not implemented yet
master
Artyom Beilis 8 years ago
parent
commit
0f5f632db6
7 changed files with 300 additions and 79 deletions
  1. +67
    -3
      cppcms/session_interface.h
  2. +27
    -4
      cppcms/session_pool.h
  3. +2
    -0
      private/cached_settings.h
  4. +1
    -1
      src/http_context.cpp
  5. +111
    -44
      src/session_interface.cpp
  6. +56
    -27
      src/session_pool.cpp
  7. +36
    -0
      tests/session_interface_test.cpp

+ 67
- 3
cppcms/session_interface.h View File

@@ -17,18 +17,24 @@
#include <cppcms/serialization_classes.h>
#include <string>
#include <map>
#include <set>
#include <memory>
#include <sstream>
#include <typeinfo>

namespace cppcms {
namespace impl {
struct cached_settings;
}
namespace http {
class context;
class request;
class response;
class cookie;
}

class session_api;
class session_pool;

///
/// \brief This exception is thrown when CSRF attempt is suspected:
@@ -42,6 +48,34 @@ public:
}
};


///
/// API to handle session cookies.
///
/// This API allows two things:
///
/// (a) Integration with 3rd part web technologies to access CppCMS session, i.e. using CppCMS session
/// from PHP or Java Servlets
///
/// (b) An API that allows to translate cookies session tracking system to a different
/// method when cookies do not suite the design - for example for internal RPC
/// systems, etc. Note incorrect use of non-cookies medium may expose you
/// to security issues
///
class CPPCMS_API session_interface_cookie_adapter : public booster::noncopyable {
public:
virtual ~session_interface_cookie_adapter();
///
/// Set a new cookie value
///
virtual void set_cookie(http::cookie const &updated_cookie) = 0;
///
/// Get value of the cookie, it is guaranteed that \a name is
/// what session_interface::session_cookie_name() returns
///
virtual std::string get_session_cookie(std::string const &name) = 0;
};

///
/// \brief This class provides an access to an application for session management
///
@@ -69,12 +103,23 @@ public:
class CPPCMS_API session_interface : private booster::noncopyable {
public:

/// \cond INTERNAL
///
/// Create cppcms::service independent session interface to be used
/// for implementing interoperability with non-cppcms based web platforms
///
session_interface(session_pool &pool,session_interface_cookie_adapter &adapter);
///
/// Creates session interface for the context - never should be used by users
/// directly
///
session_interface(http::context &);
~session_interface();
/// \endcond

///
/// destructor...
///
~session_interface();
///
/// Check if a \a key is set (assigned some value to it) in the session
///
bool is_set(std::string const &key);
@@ -268,6 +313,14 @@ public:
bool load();

///
/// Set alternative cookies interface and load session data, returns same value as load, note
/// if any data was loaded from cookies it would be discarded
///
/// It can be used for use of an alternative session state medium
///
bool set_cookie_adapter_and_reload(session_interface_cookie_adapter &adapter);
///
/// Save the session data, generally should not be called as it is saved automatically. However when
/// writing asynchronous application and using custom slow storage devices like SQL it may be useful to control
/// when and how save() is called.
@@ -335,11 +388,22 @@ public:
///
std::string get_csrf_token_cookie_name();

///
/// Get the session cookie name
///
std::string session_cookie_name();

///
/// Retrun a set of keys that are defined for a current session;
///
std::set<std::string> key_set();
private:
friend class http::response;
friend class http::request;

void init();

impl::cached_settings const &cached_settings();

struct entry;



+ 27
- 4
cppcms/session_pool.h View File

@@ -17,11 +17,14 @@

namespace cppcms {
class service;

namespace impl {
struct cached_settings;
}
namespace sessions {
class encryptor_factory;
class session_storage_factory;
};
}
namespace json { class value; }

///
/// \brief This class provides an access to session management backends an allow customization.
@@ -31,14 +34,31 @@ namespace cppcms {
///
class CPPCMS_API session_pool: public booster::noncopyable {
public:
/// \cond INTERNAL
///
/// Constructor that is used together with CppCMS service
///
session_pool(service &srv);
///
/// Constructor that is used to create independent pool to access the session storage by external tools
///
session_pool(json::value const &v);

///
/// Destructor
///
~session_pool();

///
/// Initialize the pool - must be called before get() can be used
///
/// Note: it allows to install custom session_api, encryptor or storage functionality
///
void init();

///
/// Get an actual object that is used to store/retreive session data
///
booster::shared_ptr<session_api> get();
/// \endcond

///
/// Assign your own implementation of session_api passing pointer to session_api_factory.
@@ -56,6 +76,8 @@ namespace cppcms {
void storage(std::auto_ptr<sessions::session_storage_factory> s);
private:

impl::cached_settings const &cached_settings();

void after_fork();
struct cookies_factory;
@@ -72,6 +94,7 @@ namespace cppcms {
friend struct cookies_factory;
friend struct dual_factory;
friend struct sid_factory;
friend class session_interface;
friend class gc_job;

booster::hold_ptr<_data> d;


+ 2
- 0
private/cached_settings.h View File

@@ -127,6 +127,7 @@ namespace impl {
struct cached_session {
int timeout;
std::string expire;
bool disable_automatic_load;
struct cached_cookies {
std::string prefix;
std::string domain;
@@ -140,6 +141,7 @@ namespace impl {
{
timeout = v.get("session.timeout",24*3600);
expire = v.get("session.expire","browser");
disable_automatic_load = v.get("session.disable_automatic_load",false);
cookies.prefix = v.get("session.cookies.prefix","cppcms_session");
cookies.domain = v.get("session.cookies.domain","");
cookies.path = v.get("session.cookies.path","/");


+ 1
- 1
src/http_context.cpp View File

@@ -127,7 +127,7 @@ void context::complete_response()
void context::dispatch(booster::intrusive_ptr<application> app,std::string url,bool syncronous)
{
try {
if(syncronous)
if(syncronous && !app->context().service().cached_settings().session.disable_automatic_load)
app->context().session().load();
app->main(url);
}


+ 111
- 44
src/session_interface.cpp View File

@@ -28,11 +28,13 @@
#include <sstream>
#include "string.h"

using namespace std;

namespace cppcms {

struct session_interface::_data {};
struct session_interface::_data {
session_pool *pool;
session_interface_cookie_adapter *adapter;
_data() : pool(0), adapter(0) {}
};

struct session_interface::entry {
std::string value;
@@ -50,18 +52,12 @@ namespace cppcms {
}
};

session_interface::session_interface(http::context &context) :
context_(&context),
loaded_(0),
reset_(0),
csrf_checked_(0),
csrf_do_validation_(0),
csrf_validation_(0)
void session_interface::init()
{
csrf_validation_ = context.service().cached_settings().security.csrf.enable;
csrf_do_validation_ = context.service().cached_settings().security.csrf.automatic;
timeout_val_def_=context.service().cached_settings().session.timeout;
string s_how=context.service().cached_settings().session.expire;
csrf_validation_ = cached_settings().security.csrf.enable;
csrf_do_validation_ = cached_settings().security.csrf.automatic;
timeout_val_def_=cached_settings().session.timeout;
std::string s_how=cached_settings().session.expire;
if(s_how=="fixed") {
how_def_=fixed;
}
@@ -74,7 +70,33 @@ session_interface::session_interface(http::context &context) :
else {
throw cppcms_error("Unsupported `session.expire' type `"+s_how+"'");
}
}

session_interface::session_interface(session_pool &pool,session_interface_cookie_adapter &adapter) :
context_(0),
loaded_(0),
reset_(0),
csrf_checked_(0),
csrf_do_validation_(0),
csrf_validation_(0),
d(new session_interface::_data())
{
d->pool = &pool;
d->adapter = &adapter;
init();
storage_=d->pool->get();
}

session_interface::session_interface(http::context &context) :
context_(&context),
loaded_(0),
reset_(0),
csrf_checked_(0),
csrf_do_validation_(0),
csrf_validation_(0),
d(new session_interface::_data())
{
init();
storage_=context_->service().session_pool().get();
}

@@ -95,6 +117,8 @@ bool session_interface::validate_csrf_token(std::string const &token)

void session_interface::validate_request_origin()
{
if(!context_)
throw cppcms_error("request origin validation isn't possible without http::context");
if(csrf_checked_)
return;
csrf_checked_ = 1;
@@ -136,7 +160,7 @@ bool session_interface::load()
data_copy_.clear();
timeout_val_=timeout_val_def_;
how_=how_def_;
string ar;
std::string ar;
saved_=0;
on_server_=0;
if(!storage_->load(*this,ar,timeout_in_)) {
@@ -152,6 +176,12 @@ bool session_interface::load()
on_server_=get<int>("_s");
return true;
}
bool session_interface::set_cookie_adapter_and_reload(session_interface_cookie_adapter &adapter)
{
d->adapter = &adapter;
loaded_ = 0;
return load();
}

int session_interface::cookie_age()
{
@@ -219,9 +249,9 @@ void session_interface::load_data(data_type &data,std::string const &s)
packed p(begin,end);
begin +=sizeof(p);
if(end - begin >= int(p.key_size + p.data_size)) {
string key(begin,begin+p.key_size);
std::string key(begin,begin+p.key_size);
begin+=p.key_size;
string val(begin,begin+p.data_size);
std::string val(begin,begin+p.data_size);
begin+=p.data_size;
entry &ent=data[key];
ent.exposed = p.exposed;
@@ -281,10 +311,10 @@ void session_interface::save()
}


if(new_session_ && context_->service().cached_settings().security.csrf.enable)
if(new_session_ && cached_settings().security.csrf.enable)
{
set("_csrf",generate_csrf_token());
if(context_->service().cached_settings().security.csrf.exposed)
if(cached_settings().security.csrf.exposed)
expose("_csrf");
}

@@ -305,7 +335,7 @@ void session_interface::save()
force_update=true;
}

string ar;
std::string ar;
save_data(data_,ar);
temp_cookie_.clear();
@@ -323,19 +353,19 @@ void session_interface::check()
throw cppcms_error("Session storage backend is not loaded\n");
}

string &session_interface::operator[](string const &key)
std::string &session_interface::operator[](std::string const &key)
{
check();
return data_[key].value;
}

void session_interface::erase(string const &key)
void session_interface::erase(std::string const &key)
{
check();
data_.erase(key);
}

bool session_interface::is_set(string const &key)
bool session_interface::is_set(std::string const &key)
{
check();
return data_.find(key)!=data_.end();
@@ -347,6 +377,18 @@ void session_interface::clear()
data_.clear();
}

std::set<std::string> session_interface::key_set()
{
check();
std::set<std::string> r;
for(data_type::const_iterator p=data_.begin();p!=data_.end();++p) {
if(p->first.c_str()[0]=='_')
continue;
r.insert(p->first);
}
return r;
}

std::string session_interface::get(std::string const &key,std::string const &def)
{
check();
@@ -383,22 +425,22 @@ bool session_interface::is_blocking()
return storage_ && storage_->is_blocking();
}

void session_interface::set_session_cookie(int64_t age,string const &data,string const &key)
void session_interface::set_session_cookie(int64_t age,std::string const &data,std::string const &key)
{
if(data.empty())
age=-1;
std::string cookie_name=context_->service().cached_settings().session.cookies.prefix;
std::string cookie_name=cached_settings().session.cookies.prefix;
if(!key.empty()) {
cookie_name+="_";
cookie_name+=key;
}
std::string const &domain = context_->service().cached_settings().session.cookies.domain;
std::string const &path = context_->service().cached_settings().session.cookies.path;
int time_shift = context_->service().cached_settings().session.cookies.time_shift;
bool use_age = context_->service().cached_settings().session.cookies.use_age;
bool use_exp = context_->service().cached_settings().session.cookies.use_exp;
std::string const &domain = cached_settings().session.cookies.domain;
std::string const &path = cached_settings().session.cookies.path;
int time_shift = cached_settings().session.cookies.time_shift;
bool use_age = cached_settings().session.cookies.use_age;
bool use_exp = cached_settings().session.cookies.use_exp;

bool secure = context_->service().cached_settings().session.cookies.secure;
bool secure = cached_settings().session.cookies.secure;

http::cookie the_cookie(cookie_name,util::urlencode(data),path,domain);

@@ -421,25 +463,38 @@ void session_interface::set_session_cookie(int64_t age,string const &data,string


the_cookie.secure(secure);

context_->response().set_cookie(the_cookie);
if(d->adapter)
d->adapter->set_cookie(the_cookie);
else
context_->response().set_cookie(the_cookie);
}

void session_interface::set_session_cookie(string const &data)
void session_interface::set_session_cookie(std::string const &data)
{
check();
temp_cookie_=data;
}

string session_interface::get_session_cookie()
std::string session_interface::session_cookie_name()
{
return cached_settings().session.cookies.prefix;
}

std::string session_interface::get_session_cookie()
{
check();
string name=context_->service().cached_settings().session.cookies.prefix;
http::request::cookies_type const &cookies = context_->request().cookies();
http::request::cookies_type::const_iterator p=cookies.find(name);
if(p==cookies.end())
return "";
return p->second.value();
std::string const &name=cached_settings().session.cookies.prefix;
if(d->adapter) {
return d->adapter->get_session_cookie(name);
}
else {
http::request::cookies_type const &cookies = context_->request().cookies();
http::request::cookies_type::const_iterator p=cookies.find(name);
if(p==cookies.end())
return std::string();
return p->second.value();
}
}
bool session_interface::is_exposed(std::string const &key)
@@ -450,12 +505,12 @@ bool session_interface::is_exposed(std::string const &key)
return false;
}

void session_interface::expose(string const &key,bool exp)
void session_interface::expose(std::string const &key,bool exp)
{
data_[key].exposed=exp;
}

void session_interface::hide(string const &key)
void session_interface::hide(std::string const &key)
{
check();
expose(key,false);
@@ -525,7 +580,19 @@ std::string session_interface::get_csrf_token()

std::string session_interface::get_csrf_token_cookie_name()
{
return context_->service().cached_settings().session.cookies.prefix + "__csrf"; // one for suffix and one for _csrf
return cached_settings().session.cookies.prefix + "__csrf"; // one for suffix and one for _csrf
}

impl::cached_settings const &session_interface::cached_settings()
{
if(context_)
return context_->service().cached_settings();
else
return d->pool->cached_settings();
}

session_interface_cookie_adapter::~session_interface_cookie_adapter()
{
}




+ 56
- 27
src/session_pool.cpp View File

@@ -39,11 +39,14 @@ namespace boost = cppcms_boost;
#include <booster/callback.h>
#include <booster/posix_time.h>

#include "cached_settings.h"

namespace cppcms {
struct session_pool::_data
{
booster::shared_object module;
cppcms::json::value settings;
booster::hold_ptr<impl::cached_settings> cached_settings;
};

using namespace cppcms::sessions;
@@ -149,17 +152,18 @@ private:

void session_pool::init()
{
service &srv=*service_;
cppcms::json::value const &settings = (service_ ? service_->settings() : d->settings);

if(backend_.get())
return;

std::string location=srv.settings().get("session.location","none");
std::string location=settings.get("session.location","none");

if((location == "client" || location=="both") && !encryptor_.get()) {
using namespace cppcms::sessions::impl;
std::string enc=srv.settings().get("session.client.encryptor","");
std::string mac=srv.settings().get("session.client.hmac","");
std::string cbc=srv.settings().get("session.client.cbc","");
std::string enc=settings.get("session.client.encryptor","");
std::string mac=settings.get("session.client.hmac","");
std::string cbc=settings.get("session.client.cbc","");
if(enc.empty() && mac.empty() && cbc.empty()) {
throw cppcms_error("Using clinet session storage without encryption method");
@@ -175,10 +179,10 @@ void session_pool::init()
if(!enc.empty()) {
crypto::key k;

std::string key_file = srv.settings().get("session.client.key_file","");
std::string key_file = settings.get("session.client.key_file","");

if(key_file.empty())
k=crypto::key(srv.settings().get<std::string>("session.client.key"));
k=crypto::key(settings.get<std::string>("session.client.key"));
else
k.read_from_file(key_file);

@@ -197,9 +201,9 @@ void session_pool::init()
}
else {
crypto::key hmac_key;
std::string hmac_key_file = srv.settings().get("session.client.hmac_key_file","");
std::string hmac_key_file = settings.get("session.client.hmac_key_file","");
if(hmac_key_file.empty())
hmac_key = crypto::key(srv.settings().get<std::string>("session.client.hmac_key"));
hmac_key = crypto::key(settings.get<std::string>("session.client.hmac_key"));
else
hmac_key.read_from_file(hmac_key_file);

@@ -208,10 +212,10 @@ void session_pool::init()
}
else {
crypto::key cbc_key;
std::string cbc_key_file = srv.settings().get("session.client.cbc_key_file","");
std::string cbc_key_file = settings.get("session.client.cbc_key_file","");

if(cbc_key_file.empty())
cbc_key = crypto::key(srv.settings().get<std::string>("session.client.cbc_key"));
cbc_key = crypto::key(settings.get<std::string>("session.client.cbc_key"));
else
cbc_key.read_from_file(cbc_key_file);

@@ -222,29 +226,41 @@ void session_pool::init()
encryptor(factory);
}
if((location == "server" || location == "both") && !storage_.get()) {
std::string stor=srv.settings().get<std::string>("session.server.storage");
std::string stor=settings.get<std::string>("session.server.storage");
std::auto_ptr<sessions::session_storage_factory> factory;
if(stor == "files") {
std::string dir = srv.settings().get("session.server.dir","");
std::string dir = settings.get("session.server.dir","");
#ifdef CPPCMS_WIN_NATIVE
factory.reset(new session_file_storage_factory(dir));
#else
bool sharing = srv.settings().get("session.server.shared",true);
int threads = srv.threads_no();
int procs = srv.procs_no();
if(procs == 0) procs=1;
factory.reset(new session_file_storage_factory(dir,threads*procs,procs,sharing));
if(!service_) {
if(!settings.get("session.server.shared",true)) {
throw cppcms_error("When using external session management with file storage "
"session.server.shared must be true");
}
factory.reset(new session_file_storage_factory(dir,booster::thread::hardware_concurrency()+1,2,true));
}
else {
bool sharing = settings.get("session.server.shared",true);
int threads = service_->threads_no();
int procs = service_->procs_no();
if(procs == 0) procs=1;
factory.reset(new session_file_storage_factory(dir,threads*procs,procs,sharing));
}
#endif
}
else if(stor == "memory") {
if(srv.procs_no() > 1)
if(!service_) {
throw cppcms_error("Can't use memory storage for external session management");
}
if(service_->procs_no() > 1)
throw cppcms_error("Can't use memory storage with more then 1 worker process");
factory.reset(new session_memory_storage_factory());
}
else if(stor == "external") {
std::string so = srv.settings().get<std::string>("session.server.shared_object","");
std::string module = srv.settings().get<std::string>("session.server.module","");
std::string entry_point = srv.settings().get<std::string>("session.server.entry_point","sessions_generator");
std::string so = settings.get<std::string>("session.server.shared_object","");
std::string module = settings.get<std::string>("session.server.module","");
std::string entry_point = settings.get<std::string>("session.server.entry_point","sessions_generator");
if(so.empty() && module.empty())
throw cppcms_error( "sessions_pool: session.storage=external "
"and neither session.server.shared_object "
@@ -262,14 +278,14 @@ void session_pool::init()
}
cppcms_session_storage_generator_type f=0;
d->module.symbol(f,entry_point);
factory.reset(f(srv.settings().find("session.server.settings")));
factory.reset(f(settings.find("session.server.settings")));
}
#ifndef CPPCMS_NO_TCP_CACHE
else if(stor == "network") {
typedef std::vector<std::string> ips_type;
typedef std::vector<int> ports_type;
ips_type ips = srv.settings().get<ips_type>("session.server.ips");
ports_type ports = srv.settings().get<ports_type>("session.server.ports");
ips_type ips = settings.get<ips_type>("session.server.ips");
ports_type ports = settings.get<ports_type>("session.server.ports");
if(ips.size() != ports.size())
throw cppcms_error( "sessions_pool: session.server.ips and "
"session.server.ports are not of the same size");
@@ -292,7 +308,7 @@ void session_pool::init()
backend(f);
}
else if(location == "both") {
unsigned limit=srv.settings().get("session.client_size_limit",2048);
unsigned limit=settings.get("session.client_size_limit",2048);
std::auto_ptr<session_api_factory> f(new dual_factory(limit,this));
backend(f);
}
@@ -301,7 +317,8 @@ void session_pool::init()
else
throw cppcms_error("Unknown location");

service_->after_fork(boost::bind(&session_pool::after_fork,this));
if(service_)
service_->after_fork(boost::bind(&session_pool::after_fork,this));
}

session_pool::session_pool(service &srv) :
@@ -310,6 +327,13 @@ session_pool::session_pool(service &srv) :
{
}

session_pool::session_pool(cppcms::json::value const &v) :
d(new _data()),
service_(0)
{
d->settings = v;
d->cached_settings.reset(new cppcms::impl::cached_settings(v));
}
void session_pool::after_fork()
{
if(backend_.get() && backend_->requires_gc()) {
@@ -348,4 +372,9 @@ void session_pool::storage(std::auto_ptr<sessions::session_storage_factory> s)
storage_=s;
}

cppcms::impl::cached_settings const &session_pool::cached_settings()
{
return service_? service_->cached_settings() : *d->cached_settings;
}

} /// cppcms

+ 36
- 0
tests/session_interface_test.cpp View File

@@ -11,6 +11,7 @@
#include <cppcms/http_request.h>
#include <cppcms/http_response.h>
#include <cppcms/http_context.h>
#include <cppcms/http_cookie.h>
#include <cppcms/session_interface.h>
#include <cppcms/serialization.h>
#include <cppcms/json.h>
@@ -27,6 +28,37 @@ struct mydata : public cppcms::serializable {
}
};

void test_set(cppcms::session_interface &interface,std::string const &values)
{
std::set<std::string> keys = interface.key_set();
std::string res;
for(std::set<std::string>::const_iterator p=keys.begin();p!=keys.end();++p) {
if(res.empty())
res = *p;
else
res += "|" + *p;
}
if(res!=values)
throw std::runtime_error("Key set is invalid:(" + res +")!=(" + values +")");
}

class adapter : public cppcms::session_interface_cookie_adapter {
public:
void set_cookie(cppcms::http::cookie const &updated_cookie)
{
if(updated_cookie.name() == ctx_->session().session_cookie_name()) {
ctx_->response().out() << updated_cookie.value();
}
}
std::string get_session_cookie(std::string const &/*name*/)
{
return ctx_->request().get("sid");
}
private:
cppcms::http::context *ctx_;
};

class unit_test : public cppcms::application {
public:
unit_test(cppcms::service &s) : cppcms::application(s)
@@ -104,7 +136,9 @@ public:
else if(u=="/api") {
try {
// Fix Me Later
test_set(session(),"");
session().set("x","10");
test_set(session(),"x");
TEST(session().get<std::string>("x")=="10");
TEST(session().get<int>("x")==10);
TEST(session().get<double>("x")==10.0);
@@ -112,6 +146,7 @@ public:
TEST(session().get("x","default")=="10");
TEST(session()["x"]=="10");
TEST(session()["z"]=="");
test_set(session(),"x|z");
session()["z"]="test";
TEST(session()["z"]=="test");

@@ -120,6 +155,7 @@ public:
a.y=20;
session().store_data("tmp",a);
session().fetch_data("tmp",b);
test_set(session(),"tmp|x|z");
TEST(b.x==10);
TEST(b.y==20);



Loading…
Cancel
Save