pion  5.0.6
src/http_cookie_auth.cpp
00001 // ---------------------------------------------------------------------
00002 // pion:  a Boost C++ framework for building lightweight HTTP interfaces
00003 // ---------------------------------------------------------------------
00004 // Copyright (C) 2007-2014 Splunk Inc.  (https://github.com/splunk/pion)
00005 //
00006 // Distributed under the Boost Software License, Version 1.0.
00007 // See http://www.boost.org/LICENSE_1_0.txt
00008 //
00009 
00010 #include <boost/algorithm/string.hpp>
00011 #include <pion/algorithm.hpp>
00012 #include <pion/http/cookie_auth.hpp>
00013 #include <pion/http/response_writer.hpp>
00014 #include <pion/http/server.hpp>
00015 #include <ctime>
00016 
00017 
00018 namespace pion {    // begin namespace pion
00019 namespace http {    // begin namespace http
00020     
00021     
00022 // static members of cookie_auth
00023 
00024 const unsigned int  cookie_auth::CACHE_EXPIRATION = 3600;    // 1 hour
00025 const unsigned int  cookie_auth::RANDOM_COOKIE_BYTES = 20;
00026 const std::string   cookie_auth::AUTH_COOKIE_NAME = "pion_session_id";   
00027 
00028 
00029 // cookie_auth member functions
00030 
00031 cookie_auth::cookie_auth(user_manager_ptr userManager,
00032                                const std::string& login,
00033                                const std::string& logout,
00034                                const std::string& redirect)
00035     : http::auth(userManager), m_login(login), m_logout(logout), m_redirect(redirect),
00036     m_random_gen(), m_random_range(0, 255), m_random_die(m_random_gen, m_random_range),
00037     m_cache_cleanup_time(boost::posix_time::second_clock::universal_time())
00038 {
00039     // set logger for this class
00040     set_logger(PION_GET_LOGGER("pion.http.cookie_auth"));
00041 
00042     // Seed random number generator with current time as time_t int value, cast to the required type.
00043     // (Note that boost::mt19937::result_type is boost::uint32_t, and casting to an unsigned n-bit integer is
00044     // defined by the standard to keep the lower n bits.  Since ::time() returns seconds since Jan 1, 1970, 
00045     // it will be a long time before we lose any entropy here, even if time_t is a 64-bit int.)
00046     m_random_gen.seed(static_cast<boost::mt19937::result_type>(::time(NULL)));
00047 
00048     // generate some random numbers to increase entropy of the rng
00049     for (unsigned int n = 0; n < 100; ++n)
00050         m_random_die();
00051 }
00052     
00053 bool cookie_auth::handle_request(http::request_ptr& http_request_ptr, tcp::connection_ptr& tcp_conn)
00054 {
00055     if (process_login(http_request_ptr,tcp_conn)) {
00056         return false; // we processed login/logout request, no future processing for this request permitted
00057     }
00058 
00059     if (!need_authentication(http_request_ptr)) {
00060         return true; // this request does not require authentication
00061     }
00062 
00063     // check if it is redirection page.. If yes, then do not test its credentials ( as used for login)
00064     if (!m_redirect.empty() && m_redirect==http_request_ptr->get_resource()) {
00065         return true; // this request does not require authentication
00066     }
00067     
00068     // check cache for expiration
00069     boost::posix_time::ptime time_now(boost::posix_time::second_clock::universal_time());
00070     expire_cache(time_now);
00071 
00072     // if we are here, we need to check if access authorized...
00073     const std::string auth_cookie(http_request_ptr->get_cookie(AUTH_COOKIE_NAME));
00074     if (! auth_cookie.empty()) {
00075         // check if this cookie is in user cache
00076         boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00077         user_cache_type::iterator user_cache_itr=m_user_cache.find(auth_cookie);
00078         if (user_cache_itr != m_user_cache.end()) {
00079             // we find those credential in our cache...
00080             // we can approve authorization now!
00081             http_request_ptr->set_user(user_cache_itr->second.second);
00082             // and update cache timeout
00083             user_cache_itr->second.first = time_now;
00084             return true;
00085         }
00086     }
00087 
00088     // user not found
00089     handle_unauthorized(http_request_ptr,tcp_conn);
00090     return false;
00091 }
00092     
00093 void cookie_auth::set_option(const std::string& name, const std::string& value) 
00094 {
00095     if (name=="login")
00096         m_login = value;
00097     else if (name=="logout")
00098         m_logout = value;
00099     else if (name=="redirect")
00100         m_redirect = value;
00101     else
00102         BOOST_THROW_EXCEPTION( error::bad_arg() << error::errinfo_arg_name(name) );
00103 }
00104 
00105 bool cookie_auth::process_login(http::request_ptr& http_request_ptr, tcp::connection_ptr& tcp_conn)
00106 {
00107     // strip off trailing slash if the request has one
00108     std::string resource(http::server::strip_trailing_slash(http_request_ptr->get_resource()));
00109 
00110     if (resource != m_login && resource != m_logout) {
00111         return false; // no login processing done
00112     }
00113 
00114     std::string redirect_url = http_request_ptr->get_query("url");
00115     std::string new_cookie;
00116     bool delete_cookie = false;
00117 
00118     if (resource == m_login) {
00119         // process login
00120         // check username
00121         std::string username = http_request_ptr->get_query("user");
00122         std::string password = http_request_ptr->get_query("pass");
00123 
00124         // match username/password
00125         user_ptr user=m_user_manager->get_user(username,password);
00126         if (!user) { // authentication failed, process as in case of failed authentication...
00127             handle_unauthorized(http_request_ptr,tcp_conn);
00128             return true;
00129         }
00130         // ok we have a new user session, create  a new cookie, add to cache
00131 
00132         // create random cookie
00133         std::string rand_binary;
00134         rand_binary.reserve(RANDOM_COOKIE_BYTES);
00135         for (unsigned int i=0; i<RANDOM_COOKIE_BYTES ; i++) {
00136             rand_binary += static_cast<unsigned char>(m_random_die());
00137         }
00138         algorithm::base64_encode(rand_binary, new_cookie);
00139 
00140         // add new session to cache
00141         boost::posix_time::ptime time_now(boost::posix_time::second_clock::universal_time());
00142         boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00143         m_user_cache.insert(std::make_pair(new_cookie,std::make_pair(time_now,user)));
00144     } else {
00145         // process logout sequence
00146         // if auth cookie presented - clean cache out
00147         const std::string auth_cookie(http_request_ptr->get_cookie(AUTH_COOKIE_NAME));
00148         if (! auth_cookie.empty()) {
00149             boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00150             user_cache_type::iterator user_cache_itr=m_user_cache.find(auth_cookie);
00151             if (user_cache_itr!=m_user_cache.end()) {
00152                 m_user_cache.erase(user_cache_itr);
00153             }
00154         }
00155         // and remove cookie from browser
00156         delete_cookie = true;
00157     }
00158     
00159     // if redirect defined - send redirect
00160     if (! redirect_url.empty()) {
00161         handle_redirection(http_request_ptr,tcp_conn,redirect_url,new_cookie,delete_cookie);
00162     } else {
00163         // otherwise - OK
00164         handle_ok(http_request_ptr,tcp_conn,new_cookie,delete_cookie);
00165     }
00166 
00167     // yes, we processed login/logout somehow
00168     return true;
00169 }
00170 
00171 void cookie_auth::handle_unauthorized(http::request_ptr& http_request_ptr,
00172     tcp::connection_ptr& tcp_conn)
00173 {
00174     // if redirection option is used, send redirect
00175     if (!m_redirect.empty()) {
00176         handle_redirection(http_request_ptr,tcp_conn,m_redirect,"",false);
00177         return;
00178     }
00179 
00180     // authentication failed, send 401.....
00181     static const std::string CONTENT =
00182         " <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\""
00183         "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">"
00184         "<HTML>"
00185         "<HEAD>"
00186         "<TITLE>Error</TITLE>"
00187         "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">"
00188         "</HEAD>"
00189         "<BODY><H1>401 Unauthorized.</H1></BODY>"
00190         "</HTML> ";
00191     http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00192     boost::bind(&tcp::connection::finish, tcp_conn)));
00193     writer->get_response().set_status_code(http::types::RESPONSE_CODE_UNAUTHORIZED);
00194     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_UNAUTHORIZED);
00195     writer->write_no_copy(CONTENT);
00196     writer->send();
00197 }
00198 
00199 void cookie_auth::handle_redirection(http::request_ptr& http_request_ptr,
00200                                         tcp::connection_ptr& tcp_conn,
00201                                         const std::string &redirection_url,
00202                                         const std::string &new_cookie,
00203                                         bool delete_cookie
00204                                         )
00205 {
00206     // authentication failed, send 302.....
00207     static const std::string CONTENT =
00208         " <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\""
00209         "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">"
00210         "<HTML>"
00211         "<HEAD>"
00212         "<TITLE>Redirect</TITLE>"
00213         "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">"
00214         "</HEAD>"
00215         "<BODY><H1>302 Found.</H1></BODY>"
00216         "</HTML> ";
00217     http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00218         boost::bind(&tcp::connection::finish, tcp_conn)));
00219     writer->get_response().set_status_code(http::types::RESPONSE_CODE_FOUND);
00220     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_FOUND);
00221     writer->get_response().add_header(http::types::HEADER_LOCATION, redirection_url);
00222     // Note: use empty pass "" while setting cookies to workaround IE/FF difference
00223     // It is assumed that request url points to the root
00224     // ToDo: find a better workaround
00225     if (delete_cookie) {
00226         // remove cookie
00227         writer->get_response().delete_cookie(AUTH_COOKIE_NAME,"");
00228     } else if (!new_cookie.empty()) {
00229         // set up a new cookie
00230         writer->get_response().set_cookie(AUTH_COOKIE_NAME, new_cookie,"");
00231     }
00232 
00233     writer->write_no_copy(CONTENT);
00234     writer->send();
00235 }
00236 
00237 void cookie_auth::handle_ok(http::request_ptr& http_request_ptr,
00238                               tcp::connection_ptr& tcp_conn,
00239                               const std::string &new_cookie,
00240                               bool delete_cookie
00241                               )
00242 {
00243     // send 204 (No Content) response
00244     http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00245         boost::bind(&tcp::connection::finish, tcp_conn)));
00246     writer->get_response().set_status_code(http::types::RESPONSE_CODE_NO_CONTENT);
00247     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NO_CONTENT);
00248     // Note: use empty pass "" while setting cookies to workaround IE/FF difference
00249     // It is assumed that request url points to the root
00250     // ToDo: find a better workaround
00251     if (delete_cookie) {
00252         // remove cookie
00253         writer->get_response().delete_cookie(AUTH_COOKIE_NAME,"");
00254     } else if(!new_cookie.empty()) {
00255         // set up a new cookie
00256         writer->get_response().set_cookie(AUTH_COOKIE_NAME, new_cookie,"");
00257     }
00258     writer->send();
00259 }
00260 
00261 void cookie_auth::expire_cache(const boost::posix_time::ptime &time_now)
00262 {
00263     if (time_now > m_cache_cleanup_time + boost::posix_time::seconds(CACHE_EXPIRATION)) {
00264         // expire cache
00265         boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00266         user_cache_type::iterator i;
00267         user_cache_type::iterator next=m_user_cache.begin();
00268         while (next!=m_user_cache.end()) {
00269             i=next;
00270             ++next;
00271             if (time_now > i->second.first + boost::posix_time::seconds(CACHE_EXPIRATION)) {
00272                 // ok - this is an old record.. expire it now
00273                 m_user_cache.erase(i);
00274             }
00275         }
00276         m_cache_cleanup_time = time_now;
00277     }
00278 }
00279 
00280 }   // end namespace http
00281 }   // end namespace pion