pion  5.0.6
http_cookie_auth.cpp
1 // ---------------------------------------------------------------------
2 // pion: a Boost C++ framework for building lightweight HTTP interfaces
3 // ---------------------------------------------------------------------
4 // Copyright (C) 2007-2014 Splunk Inc. (https://github.com/splunk/pion)
5 //
6 // Distributed under the Boost Software License, Version 1.0.
7 // See http://www.boost.org/LICENSE_1_0.txt
8 //
9 
10 #include <boost/algorithm/string.hpp>
11 #include <pion/algorithm.hpp>
12 #include <pion/http/cookie_auth.hpp>
13 #include <pion/http/response_writer.hpp>
14 #include <pion/http/server.hpp>
15 #include <ctime>
16 
17 
18 namespace pion { // begin namespace pion
19 namespace http { // begin namespace http
20 
21 
22 // static members of cookie_auth
23 
24 const unsigned int cookie_auth::CACHE_EXPIRATION = 3600; // 1 hour
25 const unsigned int cookie_auth::RANDOM_COOKIE_BYTES = 20;
26 const std::string cookie_auth::AUTH_COOKIE_NAME = "pion_session_id";
27 
28 
29 // cookie_auth member functions
30 
31 cookie_auth::cookie_auth(user_manager_ptr userManager,
32  const std::string& login,
33  const std::string& logout,
34  const std::string& redirect)
35  : http::auth(userManager), m_login(login), m_logout(logout), m_redirect(redirect),
36  m_random_gen(), m_random_range(0, 255), m_random_die(m_random_gen, m_random_range),
37  m_cache_cleanup_time(boost::posix_time::second_clock::universal_time())
38 {
39  // set logger for this class
40  set_logger(PION_GET_LOGGER("pion.http.cookie_auth"));
41 
42  // Seed random number generator with current time as time_t int value, cast to the required type.
43  // (Note that boost::mt19937::result_type is boost::uint32_t, and casting to an unsigned n-bit integer is
44  // defined by the standard to keep the lower n bits. Since ::time() returns seconds since Jan 1, 1970,
45  // it will be a long time before we lose any entropy here, even if time_t is a 64-bit int.)
46  m_random_gen.seed(static_cast<boost::mt19937::result_type>(::time(NULL)));
47 
48  // generate some random numbers to increase entropy of the rng
49  for (unsigned int n = 0; n < 100; ++n)
50  m_random_die();
51 }
52 
53 bool cookie_auth::handle_request(const http::request_ptr& http_request_ptr, const tcp::connection_ptr& tcp_conn)
54 {
55  if (process_login(http_request_ptr,tcp_conn)) {
56  return false; // we processed login/logout request, no future processing for this request permitted
57  }
58 
59  if (!need_authentication(http_request_ptr)) {
60  return true; // this request does not require authentication
61  }
62 
63  // check if it is redirection page.. If yes, then do not test its credentials ( as used for login)
64  if (!m_redirect.empty() && m_redirect==http_request_ptr->get_resource()) {
65  return true; // this request does not require authentication
66  }
67 
68  // check cache for expiration
69  boost::posix_time::ptime time_now(boost::posix_time::second_clock::universal_time());
70  expire_cache(time_now);
71 
72  // if we are here, we need to check if access authorized...
73  const std::string auth_cookie(http_request_ptr->get_cookie(AUTH_COOKIE_NAME));
74  if (! auth_cookie.empty()) {
75  // check if this cookie is in user cache
76  boost::mutex::scoped_lock cache_lock(m_cache_mutex);
77  user_cache_type::iterator user_cache_itr=m_user_cache.find(auth_cookie);
78  if (user_cache_itr != m_user_cache.end()) {
79  // we find those credential in our cache...
80  // we can approve authorization now!
81  http_request_ptr->set_user(user_cache_itr->second.second);
82  // and update cache timeout
83  user_cache_itr->second.first = time_now;
84  return true;
85  }
86  }
87 
88  // user not found
89  handle_unauthorized(http_request_ptr,tcp_conn);
90  return false;
91 }
92 
93 void cookie_auth::set_option(const std::string& name, const std::string& value)
94 {
95  if (name=="login")
96  m_login = value;
97  else if (name=="logout")
98  m_logout = value;
99  else if (name=="redirect")
100  m_redirect = value;
101  else
102  BOOST_THROW_EXCEPTION( error::bad_arg() << error::errinfo_arg_name(name) );
103 }
104 
105 bool cookie_auth::process_login(const http::request_ptr& http_request_ptr, const tcp::connection_ptr& tcp_conn)
106 {
107  // strip off trailing slash if the request has one
108  std::string resource(http::server::strip_trailing_slash(http_request_ptr->get_resource()));
109 
110  if (resource != m_login && resource != m_logout) {
111  return false; // no login processing done
112  }
113 
114  std::string redirect_url = http_request_ptr->get_query("url");
115  std::string new_cookie;
116  bool delete_cookie = false;
117 
118  if (resource == m_login) {
119  // process login
120  // check username
121  std::string username = http_request_ptr->get_query("user");
122  std::string password = http_request_ptr->get_query("pass");
123 
124  // match username/password
125  user_ptr user=m_user_manager->get_user(username,password);
126  if (!user) { // authentication failed, process as in case of failed authentication...
127  handle_unauthorized(http_request_ptr,tcp_conn);
128  return true;
129  }
130  // ok we have a new user session, create a new cookie, add to cache
131 
132  // create random cookie
133  std::string rand_binary;
134  rand_binary.reserve(RANDOM_COOKIE_BYTES);
135  for (unsigned int i=0; i<RANDOM_COOKIE_BYTES ; i++) {
136  rand_binary += static_cast<unsigned char>(m_random_die());
137  }
138  algorithm::base64_encode(rand_binary, new_cookie);
139 
140  // add new session to cache
141  boost::posix_time::ptime time_now(boost::posix_time::second_clock::universal_time());
142  boost::mutex::scoped_lock cache_lock(m_cache_mutex);
143  m_user_cache.insert(std::make_pair(new_cookie,std::make_pair(time_now,user)));
144  } else {
145  // process logout sequence
146  // if auth cookie presented - clean cache out
147  const std::string auth_cookie(http_request_ptr->get_cookie(AUTH_COOKIE_NAME));
148  if (! auth_cookie.empty()) {
149  boost::mutex::scoped_lock cache_lock(m_cache_mutex);
150  user_cache_type::iterator user_cache_itr=m_user_cache.find(auth_cookie);
151  if (user_cache_itr!=m_user_cache.end()) {
152  m_user_cache.erase(user_cache_itr);
153  }
154  }
155  // and remove cookie from browser
156  delete_cookie = true;
157  }
158 
159  // if redirect defined - send redirect
160  if (! redirect_url.empty()) {
161  handle_redirection(http_request_ptr,tcp_conn,redirect_url,new_cookie,delete_cookie);
162  } else {
163  // otherwise - OK
164  handle_ok(http_request_ptr,tcp_conn,new_cookie,delete_cookie);
165  }
166 
167  // yes, we processed login/logout somehow
168  return true;
169 }
170 
171 void cookie_auth::handle_unauthorized(const http::request_ptr& http_request_ptr,
172  const tcp::connection_ptr& tcp_conn)
173 {
174  // if redirection option is used, send redirect
175  if (!m_redirect.empty()) {
176  handle_redirection(http_request_ptr,tcp_conn,m_redirect,"",false);
177  return;
178  }
179 
180  // authentication failed, send 401.....
181  static const std::string CONTENT =
182  " <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\""
183  "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">"
184  "<HTML>"
185  "<HEAD>"
186  "<TITLE>Error</TITLE>"
187  "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">"
188  "</HEAD>"
189  "<BODY><H1>401 Unauthorized.</H1></BODY>"
190  "</HTML> ";
191  http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
192  boost::bind(&tcp::connection::finish, tcp_conn)));
193  writer->get_response().set_status_code(http::types::RESPONSE_CODE_UNAUTHORIZED);
194  writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_UNAUTHORIZED);
195  writer->write_no_copy(CONTENT);
196  writer->send();
197 }
198 
199 void cookie_auth::handle_redirection(const http::request_ptr& http_request_ptr,
200  const tcp::connection_ptr& tcp_conn,
201  const std::string &redirection_url,
202  const std::string &new_cookie,
203  bool delete_cookie
204  )
205 {
206  // authentication failed, send 302.....
207  static const std::string CONTENT =
208  " <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\""
209  "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">"
210  "<HTML>"
211  "<HEAD>"
212  "<TITLE>Redirect</TITLE>"
213  "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">"
214  "</HEAD>"
215  "<BODY><H1>302 Found.</H1></BODY>"
216  "</HTML> ";
217  http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
218  boost::bind(&tcp::connection::finish, tcp_conn)));
219  writer->get_response().set_status_code(http::types::RESPONSE_CODE_FOUND);
220  writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_FOUND);
221  writer->get_response().add_header(http::types::HEADER_LOCATION, redirection_url);
222  // Note: use empty pass "" while setting cookies to workaround IE/FF difference
223  // It is assumed that request url points to the root
224  // ToDo: find a better workaround
225  if (delete_cookie) {
226  // remove cookie
227  writer->get_response().delete_cookie(AUTH_COOKIE_NAME,"");
228  } else if (!new_cookie.empty()) {
229  // set up a new cookie
230  writer->get_response().set_cookie(AUTH_COOKIE_NAME, new_cookie,"");
231  }
232 
233  writer->write_no_copy(CONTENT);
234  writer->send();
235 }
236 
237 void cookie_auth::handle_ok(const http::request_ptr& http_request_ptr,
238  const tcp::connection_ptr& tcp_conn,
239  const std::string &new_cookie,
240  bool delete_cookie
241  )
242 {
243  // send 204 (No Content) response
244  http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
245  boost::bind(&tcp::connection::finish, tcp_conn)));
246  writer->get_response().set_status_code(http::types::RESPONSE_CODE_NO_CONTENT);
247  writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NO_CONTENT);
248  // Note: use empty pass "" while setting cookies to workaround IE/FF difference
249  // It is assumed that request url points to the root
250  // ToDo: find a better workaround
251  if (delete_cookie) {
252  // remove cookie
253  writer->get_response().delete_cookie(AUTH_COOKIE_NAME,"");
254  } else if(!new_cookie.empty()) {
255  // set up a new cookie
256  writer->get_response().set_cookie(AUTH_COOKIE_NAME, new_cookie,"");
257  }
258  writer->send();
259 }
260 
261 void cookie_auth::expire_cache(const boost::posix_time::ptime &time_now)
262 {
263  if (time_now > m_cache_cleanup_time + boost::posix_time::seconds(CACHE_EXPIRATION)) {
264  // expire cache
265  boost::mutex::scoped_lock cache_lock(m_cache_mutex);
266  user_cache_type::iterator i;
267  user_cache_type::iterator next=m_user_cache.begin();
268  while (next!=m_user_cache.end()) {
269  i=next;
270  ++next;
271  if (time_now > i->second.first + boost::posix_time::seconds(CACHE_EXPIRATION)) {
272  // ok - this is an old record.. expire it now
273  m_user_cache.erase(i);
274  }
275  }
276  m_cache_cleanup_time = time_now;
277  }
278 }
279 
280 } // end namespace http
281 } // end namespace pion
void handle_unauthorized(const http::request_ptr &http_request_ptr, const tcp::connection_ptr &tcp_conn)
virtual bool handle_request(const http::request_ptr &http_request_ptr, const tcp::connection_ptr &tcp_conn)
user_manager_ptr m_user_manager
container used to manager user objects
Definition: auth.hpp:156
static boost::shared_ptr< response_writer > create(const tcp::connection_ptr &tcp_conn, const http::response_ptr &http_response_ptr, finished_handler_t handler=finished_handler_t())
bool process_login(const http::request_ptr &http_request_ptr, const tcp::connection_ptr &tcp_conn)
static std::string strip_trailing_slash(const std::string &str)
Definition: server.hpp:160
void set_logger(logger log_ptr)
sets the logger to be used
Definition: auth.hpp:149
bool need_authentication(http::request_ptr const &http_request_ptr) const
Definition: http_auth.cpp:37
void handle_redirection(const http::request_ptr &http_request_ptr, const tcp::connection_ptr &tcp_conn, const std::string &redirection_url, const std::string &new_cookie="", bool delete_cookie=false)
cookie_auth(user_manager_ptr userManager, const std::string &login="/login", const std::string &logout="/logout", const std::string &redirect="")
virtual void set_option(const std::string &name, const std::string &value)
void handle_ok(const http::request_ptr &http_request_ptr, const tcp::connection_ptr &tcp_conn, const std::string &new_cookie="", bool delete_cookie=false)
exception thrown for an invalid configuration argument or option
Definition: error.hpp:132
static bool base64_encode(std::string const &input, std::string &output)
Definition: algorithm.cpp:104
void expire_cache(const boost::posix_time::ptime &time_now)