ChipMaster's trial hacks on C++CMS starting with v1.2.1. Not sure I'll follow on with the v2 since it looks to be breaking and mostly frivolous.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

648 lines
15 KiB

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright (C) 2008-2012 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>
  4. //
  5. // See accompanying file COPYING.TXT file for licensing details.
  6. //
  7. ///////////////////////////////////////////////////////////////////////////////
  8. #define CPPCMS_SOURCE
  9. #include <cppcms/session_interface.h>
  10. #include <cppcms/session_pool.h>
  11. #include <cppcms/session_api.h>
  12. #include <cppcms/util.h>
  13. #include <cppcms/http_context.h>
  14. #include <cppcms/http_request.h>
  15. #include <cppcms/http_response.h>
  16. #include <cppcms/http_cookie.h>
  17. #include <cppcms/cppcms_error.h>
  18. #include <cppcms/service.h>
  19. #include <cppcms/json.h>
  20. #include <cppcms/urandom.h>
  21. #include <cppcms/base64.h>
  22. #include <booster/log.h>
  23. #include <iostream>
  24. #include "cached_settings.h"
  25. #include <set>
  26. #include <sstream>
  27. #include "string.h"
  28. namespace cppcms {
  29. struct session_interface::_data {
  30. session_pool *pool;
  31. session_interface_cookie_adapter *adapter;
  32. _data() : pool(0), adapter(0) {}
  33. };
  34. struct session_interface::entry {
  35. std::string value;
  36. bool exposed;
  37. entry() : exposed(false) {}
  38. explicit entry(std::string const &s) : value(s), exposed(false) {}
  39. entry(std::string const &v,bool exp) : value(v) , exposed(exp) {}
  40. bool operator==(entry const &other) const
  41. {
  42. return value==other.value && exposed==other.exposed;
  43. }
  44. bool operator!=(entry const &other) const
  45. {
  46. return !(*this==other);
  47. }
  48. };
  49. void session_interface::init()
  50. {
  51. csrf_validation_ = cached_settings().security.csrf.enable;
  52. csrf_do_validation_ = cached_settings().security.csrf.automatic;
  53. timeout_val_def_=cached_settings().session.timeout;
  54. std::string s_how=cached_settings().session.expire;
  55. if(s_how=="fixed") {
  56. how_def_=fixed;
  57. }
  58. else if(s_how=="renew") {
  59. how_def_=renew;
  60. }
  61. else if(s_how=="browser") {
  62. how_def_=browser;
  63. }
  64. else {
  65. throw cppcms_error("Unsupported `session.expire' type `"+s_how+"'");
  66. }
  67. }
  68. session_interface::session_interface(session_pool &pool,session_interface_cookie_adapter &adapter) :
  69. context_(0),
  70. loaded_(0),
  71. reset_(0),
  72. csrf_checked_(0),
  73. csrf_do_validation_(0),
  74. csrf_validation_(0),
  75. d(new session_interface::_data())
  76. {
  77. d->pool = &pool;
  78. d->adapter = &adapter;
  79. init();
  80. storage_=d->pool->get();
  81. }
  82. session_interface::session_interface(http::context &context) :
  83. context_(&context),
  84. loaded_(0),
  85. reset_(0),
  86. csrf_checked_(0),
  87. csrf_do_validation_(0),
  88. csrf_validation_(0),
  89. d(new session_interface::_data())
  90. {
  91. init();
  92. storage_=context_->service().session_pool().get();
  93. }
  94. session_interface::~session_interface()
  95. {
  96. }
  97. void session_interface::request_origin_validation_is_required(bool v)
  98. {
  99. csrf_do_validation_ = v;
  100. }
  101. bool session_interface::validate_csrf_token(std::string const &token)
  102. {
  103. std::string session_token = get("_csrf","");
  104. return session_token.empty() || session_token == token;
  105. }
  106. void session_interface::validate_request_origin()
  107. {
  108. if(!context_)
  109. throw cppcms_error("request origin validation isn't possible without http::context");
  110. if(csrf_checked_)
  111. return;
  112. csrf_checked_ = 1;
  113. if(!csrf_validation_)
  114. return;
  115. if(!csrf_do_validation_)
  116. return;
  117. if(context_->request().request_method()!="POST")
  118. return;
  119. std::string token;
  120. typedef http::request::form_type::const_iterator iterator_type;
  121. std::pair<iterator_type,iterator_type> pair=context_->request().post().equal_range("_csrf");
  122. if(pair.first != pair.second && std::distance(pair.first,pair.second)==1)
  123. token = pair.first->second;
  124. else
  125. token = context_->request().getenv("HTTP_X_CSRFTOKEN");
  126. if(!validate_csrf_token(token)) {
  127. BOOSTER_WARNING("cppcms") <<"CSRF validation failed"
  128. <<" IP="<< context_->request().remote_addr()
  129. <<" SCRIPT_NAME=" << context_->request().script_name()
  130. <<" PATH_INFO="<<context_->request().path_info();
  131. throw request_forgery_error();
  132. }
  133. }
  134. bool session_interface::load()
  135. {
  136. if(loaded_)
  137. return false; // FIXME
  138. loaded_ = 1;
  139. if(!storage_.get())
  140. return false;
  141. data_.clear();
  142. data_copy_.clear();
  143. timeout_val_=timeout_val_def_;
  144. how_=how_def_;
  145. std::string ar;
  146. saved_=0;
  147. on_server_=0;
  148. if(!storage_->load(*this,ar,timeout_in_)) {
  149. return false;
  150. }
  151. load_data(data_,ar);
  152. data_copy_=data_;
  153. if(is_set("_t"))
  154. timeout_val_=get<int>("_t");
  155. if(is_set("_h"))
  156. how_=get<int>("_h");
  157. if(is_set("_s"))
  158. on_server_=get<int>("_s");
  159. return true;
  160. }
  161. bool session_interface::set_cookie_adapter_and_reload(session_interface_cookie_adapter &adapter)
  162. {
  163. d->adapter = &adapter;
  164. loaded_ = 0;
  165. return load();
  166. }
  167. int session_interface::cookie_age()
  168. {
  169. if(how_==browser)
  170. return 0;
  171. if(how_==renew || ( how_==fixed && new_session_ ))
  172. return timeout_val_;
  173. return timeout_in_ - time(NULL);
  174. }
  175. time_t session_interface::session_age()
  176. {
  177. if(how_==browser || how_==renew || (how_==fixed && new_session_))
  178. return timeout_val_ + time(NULL);
  179. return timeout_in_;
  180. }
  181. namespace {
  182. struct packed {
  183. uint32_t key_size : 10;
  184. uint32_t exposed : 1;
  185. uint32_t data_size : 21;
  186. packed() {}
  187. packed(unsigned ks,bool exp, unsigned ds)
  188. {
  189. if(ks >=1024)
  190. throw cppcms_error("session::save key too long");
  191. if(ds >= 1024 * 1024 * 2)
  192. throw cppcms_error("session::save value too long");
  193. key_size=ks;
  194. exposed = exp ? 1 : 0;
  195. data_size=ds;
  196. }
  197. packed(char const *start,char const *end)
  198. {
  199. if(start + 4 <= end ) {
  200. memcpy(this,start,4);
  201. }
  202. else
  203. throw cppcms_error("session::format violation -> pack");
  204. }
  205. };
  206. }
  207. void session_interface::save_data(data_type const &data,std::string &s)
  208. {
  209. s.clear();
  210. data_type::const_iterator p;
  211. for(p=data.begin();p!=data.end();++p) {
  212. packed header(p->first.size(),p->second.exposed,p->second.value.size());
  213. char *ptr=(char *)&header;
  214. s.append(ptr,ptr+sizeof(header));
  215. s.append(p->first.begin(),p->first.end());
  216. s.append(p->second.value.begin(),p->second.value.end());
  217. }
  218. }
  219. void session_interface::load_data(data_type &data,std::string const &s)
  220. {
  221. data.clear();
  222. char const *begin=s.data(),*end=begin+s.size();
  223. while(begin < end) {
  224. packed p(begin,end);
  225. begin +=sizeof(p);
  226. if(end - begin >= int(p.key_size + p.data_size)) {
  227. std::string key(begin,begin+p.key_size);
  228. begin+=p.key_size;
  229. std::string val(begin,begin+p.data_size);
  230. begin+=p.data_size;
  231. entry &ent=data[key];
  232. ent.exposed = p.exposed;
  233. ent.value.swap(val);
  234. }
  235. else {
  236. throw cppcms_error("sessions::format violation data");
  237. }
  238. }
  239. }
  240. void session_interface::update_exposed(bool force)
  241. {
  242. std::set<std::string> removed;
  243. for(data_type::iterator p=data_.begin();p!=data_.end();++p) {
  244. data_type::iterator p2=data_copy_.find(p->first);
  245. if(p->second.exposed && (force || p2==data_copy_.end() || !p2->second.exposed || p->second.value!=p2->second.value)){
  246. set_session_cookie(cookie_age(),p->second.value,p->first);
  247. }
  248. else if(!p->second.exposed && ((p2!=data_copy_.end() && p2->second.exposed) || force)) {
  249. removed.insert(p->first);
  250. }
  251. }
  252. for(data_type::iterator p=data_copy_.begin();p!=data_copy_.end();++p) {
  253. if(p->second.exposed && data_.find(p->first)==data_.end()) {
  254. removed.insert(p->first);
  255. }
  256. }
  257. if(cached_settings().session.cookies.remove_unknown_cookies) {
  258. std::string prefix = cached_settings().session.cookies.prefix + "_";
  259. if(!d->adapter) {
  260. typedef http::request::cookies_type cookies_type;
  261. cookies_type const &input_cookies = context_->request().cookies();
  262. for(cookies_type::const_iterator cp=input_cookies.begin();cp!=input_cookies.end();++cp) {
  263. if(cp->first.compare(0,prefix.size(),prefix)!=0)
  264. continue;
  265. std::string key = cp->first.substr(prefix.size());
  266. if(removed.find(key)!=removed.end())
  267. continue;
  268. data_type::iterator ptr;
  269. if((ptr = data_.find(key))==data_.end() || !ptr->second.exposed) {
  270. removed.insert(key);
  271. }
  272. }
  273. }
  274. else {
  275. std::set<std::string> cookies = d->adapter->get_cookie_names();
  276. for(std::set<std::string>::const_iterator cp=cookies.begin();cp!=cookies.end();++cp) {
  277. if(cp->compare(0,prefix.size(),prefix)!=0)
  278. continue;
  279. std::string key = cp->substr(prefix.size());
  280. if(removed.find(key)!=removed.end())
  281. continue;
  282. data_type::iterator ptr;
  283. if((ptr = data_.find(key))==data_.end() || !ptr->second.exposed) {
  284. removed.insert(key);
  285. }
  286. }
  287. }
  288. }
  289. for(std::set<std::string>::const_iterator p=removed.begin();p!=removed.end();++p)
  290. set_session_cookie(-1,"",*p);
  291. }
  292. std::string session_interface::generate_csrf_token()
  293. {
  294. unsigned char binary[6];
  295. unsigned char text[16];
  296. urandom_device dev;
  297. dev.generate(binary,sizeof(binary));
  298. unsigned char *text_begin = text;
  299. unsigned char *text_end = b64url::encode(binary,binary+sizeof(binary),text_begin);
  300. return std::string(text_begin,text_end);
  301. }
  302. void session_interface::save()
  303. {
  304. if(storage_.get()==NULL || !loaded_ || saved_)
  305. return;
  306. check();
  307. new_session_ = (data_copy_.empty() && !data_.empty()) || reset_;
  308. if(data_.empty()) {
  309. if(get_session_cookie()!="")
  310. storage_->clear(*this);
  311. update_exposed(true);
  312. return;
  313. }
  314. if(new_session_ && cached_settings().security.csrf.enable)
  315. {
  316. set("_csrf",generate_csrf_token());
  317. if(cached_settings().security.csrf.exposed)
  318. expose("_csrf");
  319. }
  320. time_t now = time(NULL);
  321. bool force_update=false;
  322. if(data_==data_copy_ && !new_session_) {
  323. if(how_==fixed) {
  324. return;
  325. }
  326. if(how_==renew || how_==browser) {
  327. int64_t delta=now + timeout_val_ - timeout_in_;
  328. if(delta < timeout_val_ * 0.1) {// Less then 10% -- no renew need
  329. return;
  330. }
  331. }
  332. force_update=true;
  333. }
  334. std::string ar;
  335. save_data(data_,ar);
  336. temp_cookie_.clear();
  337. storage_->save(*this,ar,session_age(),new_session_,on_server_);
  338. set_session_cookie(cookie_age(),temp_cookie_);
  339. temp_cookie_.clear();
  340. update_exposed(force_update);
  341. saved_=true;
  342. }
  343. void session_interface::check()
  344. {
  345. if(storage_.get()==NULL)
  346. throw cppcms_error("Session storage backend is not loaded\n");
  347. }
  348. std::string &session_interface::operator[](std::string const &key)
  349. {
  350. check();
  351. return data_[key].value;
  352. }
  353. void session_interface::erase(std::string const &key)
  354. {
  355. check();
  356. data_.erase(key);
  357. }
  358. bool session_interface::is_set(std::string const &key)
  359. {
  360. check();
  361. return data_.find(key)!=data_.end();
  362. }
  363. void session_interface::clear()
  364. {
  365. check();
  366. data_.clear();
  367. }
  368. std::set<std::string> session_interface::key_set()
  369. {
  370. check();
  371. std::set<std::string> r;
  372. for(data_type::const_iterator p=data_.begin();p!=data_.end();++p) {
  373. if(p->first.c_str()[0]=='_')
  374. continue;
  375. r.insert(p->first);
  376. }
  377. return r;
  378. }
  379. std::string session_interface::get(std::string const &key,std::string const &def)
  380. {
  381. check();
  382. data_type::const_iterator p=data_.find(key);
  383. if(p==data_.end())
  384. return def;
  385. return p->second.value;
  386. }
  387. std::string session_interface::get(std::string const &key)
  388. {
  389. check();
  390. data_type::const_iterator p=data_.find(key);
  391. if(p==data_.end())
  392. throw cppcms_error("Undefined session key "+key);
  393. return p->second.value;
  394. }
  395. void session_interface::set(std::string const &key,std::string const &v)
  396. {
  397. check();
  398. data_[key].value=v;
  399. }
  400. void session_interface::clear_session_cookie()
  401. {
  402. check();
  403. if(get_session_cookie()!="")
  404. set_session_cookie(-1,"");
  405. }
  406. bool session_interface::is_blocking()
  407. {
  408. return storage_ && storage_->is_blocking();
  409. }
  410. void session_interface::set_session_cookie(int64_t age,std::string const &data,std::string const &key)
  411. {
  412. if(data.empty())
  413. age=-1;
  414. std::string cookie_name=cached_settings().session.cookies.prefix;
  415. if(!key.empty()) {
  416. cookie_name+="_";
  417. cookie_name+=key;
  418. }
  419. std::string const &domain = cached_settings().session.cookies.domain;
  420. std::string const &path = cached_settings().session.cookies.path;
  421. int time_shift = cached_settings().session.cookies.time_shift;
  422. bool use_age = cached_settings().session.cookies.use_age;
  423. bool use_exp = cached_settings().session.cookies.use_exp;
  424. bool secure = cached_settings().session.cookies.secure;
  425. bool httponly = cached_settings().session.cookies.httponly;
  426. bool use_samesite_none = cached_settings().session.cookies.use_samesite_none;
  427. bool use_samesite_lax = cached_settings().session.cookies.use_samesite_lax;
  428. bool use_samesite_strict = cached_settings().session.cookies.use_samesite_strict;
  429. http::cookie the_cookie(cookie_name,util::urlencode(data),path,domain);
  430. if(age < 0) {
  431. if(use_age)
  432. the_cookie.max_age(0);
  433. if(use_exp)
  434. the_cookie.expires(1);
  435. }
  436. else if(age == 0) {
  437. the_cookie.browser_age();
  438. }
  439. else {
  440. if(use_age)
  441. the_cookie.max_age(age);
  442. if(use_exp) {
  443. the_cookie.expires(age + time(0) + time_shift);
  444. }
  445. }
  446. the_cookie.secure(secure);
  447. the_cookie.httponly(httponly);
  448. the_cookie.samesite_none(use_samesite_none);
  449. the_cookie.samesite_lax(use_samesite_lax);
  450. the_cookie.samesite_strict(use_samesite_strict);
  451. if(d->adapter)
  452. d->adapter->set_cookie(the_cookie);
  453. else
  454. context_->response().set_cookie(the_cookie);
  455. }
  456. void session_interface::set_session_cookie(std::string const &data)
  457. {
  458. check();
  459. temp_cookie_=data;
  460. }
  461. std::string session_interface::session_cookie_name()
  462. {
  463. return cached_settings().session.cookies.prefix;
  464. }
  465. std::string session_interface::get_session_cookie()
  466. {
  467. check();
  468. std::string const &name=cached_settings().session.cookies.prefix;
  469. if(d->adapter) {
  470. return d->adapter->get_session_cookie(name);
  471. }
  472. else {
  473. http::request::cookies_type const &cookies = context_->request().cookies();
  474. http::request::cookies_type::const_iterator p=cookies.find(name);
  475. if(p==cookies.end())
  476. return std::string();
  477. return p->second.value();
  478. }
  479. }
  480. bool session_interface::is_exposed(std::string const &key)
  481. {
  482. data_type::iterator p=data_.find(key);
  483. if(p!=data_.end())
  484. return p->second.exposed;
  485. return false;
  486. }
  487. void session_interface::expose(std::string const &key,bool exp)
  488. {
  489. data_[key].exposed=exp;
  490. }
  491. void session_interface::hide(std::string const &key)
  492. {
  493. check();
  494. expose(key,false);
  495. }
  496. void session_interface::age(int t)
  497. {
  498. check();
  499. timeout_val_=t;
  500. set("_t",t);
  501. }
  502. int session_interface::age()
  503. {
  504. check();
  505. return timeout_val_;
  506. }
  507. void session_interface::default_age()
  508. {
  509. check();
  510. erase("_t");
  511. timeout_val_=timeout_val_def_;
  512. }
  513. int session_interface::expiration()
  514. {
  515. check();
  516. return how_;
  517. }
  518. void session_interface::expiration(int h)
  519. {
  520. check();
  521. how_=h;
  522. set("_h",h);
  523. }
  524. void session_interface::default_expiration()
  525. {
  526. check();
  527. erase("_h");
  528. how_=how_def_;
  529. }
  530. void session_interface::on_server(bool srv)
  531. {
  532. check();
  533. on_server_=srv;
  534. set("_s",int(srv));
  535. }
  536. bool session_interface::on_server()
  537. {
  538. check();
  539. return on_server_;
  540. }
  541. void session_interface::reset_session()
  542. {
  543. reset_ = 1;
  544. }
  545. std::string session_interface::get_csrf_token()
  546. {
  547. return get("_csrf","");
  548. }
  549. std::string session_interface::get_csrf_token_cookie_name()
  550. {
  551. return cached_settings().session.cookies.prefix + "__csrf"; // one for suffix and one for _csrf
  552. }
  553. impl::cached_settings const &session_interface::cached_settings()
  554. {
  555. if(context_)
  556. return context_->service().cached_settings();
  557. else
  558. return d->pool->cached_settings();
  559. }
  560. session_interface_cookie_adapter::~session_interface_cookie_adapter()
  561. {
  562. }
  563. } // cppcms