# net/src/HTTPCookieAuth.cpp

    00001 // ------------------------------------------------------------------
    00002 // pion-net: a C++ framework for building lightweight HTTP interfaces
    00003 // ------------------------------------------------------------------
    00004 // Copyright (C) 2007-2008 Atomic Labs, Inc.  (http://www.atomiclabs.com)
    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/PionAlgorithms.hpp>
    00012 #include <pion/net/HTTPCookieAuth.hpp>
    00013 #include <pion/net/HTTPResponseWriter.hpp>
    00014 #include <pion/net/HTTPServer.hpp>
    00015 #include <ctime>
    00016
    00017
    00018 namespace pion {    // begin namespace pion
    00019 namespace net {     // begin namespace net (Pion Network Library)
    00020
    00021
    00022 // static members of HTTPCookieAuth
    00023
    00024 const unsigned int  HTTPCookieAuth::CACHE_EXPIRATION = 3600;    // 1 hour
    00025 const unsigned int  HTTPCookieAuth::RANDOM_COOKIE_BYTES = 20;
    00026 const std::string   HTTPCookieAuth::AUTH_COOKIE_NAME = "pion_session_id";
    00027
    00028
    00029 // HTTPCookieAuth member functions
    00030
    00031 HTTPCookieAuth::HTTPCookieAuth(PionUserManagerPtr userManager,
    00032                                const std::string& login,
    00033                                const std::string& logout,
    00034                                const std::string& redirect)
    00035     : HTTPAuth(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     setLogger(PION_GET_LOGGER("pion.net.HTTPCookieAuth"));
    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 HTTPCookieAuth::handleRequest(HTTPRequestPtr& request, TCPConnectionPtr& tcp_conn)
    00054 {
    00055     if (processLogin(request,tcp_conn)) {
    00056         return false; // we processed login/logout request, no future processing for this request permitted
    00057     }
    00058
    00059     if (!needAuthentication(request)) {
    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==request->getResource()) {
    00065         return true; // this request does not require authentication
    00066     }
    00067
    00068     // check cache for expiration
    00069     PionDateTime time_now(boost::posix_time::second_clock::universal_time());
    00070     expireCache(time_now);
    00071
    00072     // if we are here, we need to check if access authorized...
    00073     const std::string auth_cookie(request->getCookie(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         PionUserCache::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             request->setUser(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     handleUnauthorized(request,tcp_conn);
    00090     return false;
    00091 }
    00092
    00093 void HTTPCookieAuth::setOption(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         throw UnknownOptionException(name);
    00103 }
    00104
    00105 bool HTTPCookieAuth::processLogin(HTTPRequestPtr& http_request, TCPConnectionPtr& tcp_conn)
    00106 {
    00107     // strip off trailing slash if the request has one
    00108     std::string resource(HTTPServer::stripTrailingSlash(http_request->getResource()));
    00109
    00110     if (resource != m_login && resource != m_logout) {
    00111         return false; // no login processing done
    00112     }
    00113
    00114     std::string redirect_url = algo::url_decode(http_request->getQuery("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 = algo::url_decode(http_request->getQuery("user"));
    00122         std::string password = algo::url_decode(http_request->getQuery("pass"));
    00123
    00124         // match username/password
    00125         PionUserPtr user=m_user_manager->getUser(username,password);
    00126         if (!user) { // authentication failed, process as in case of failed authentication...
    00127             handleUnauthorized(http_request,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         algo::base64_encode(rand_binary, new_cookie);
    00139
    00140         // add new session to cache
    00141         PionDateTime 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->getCookie(AUTH_COOKIE_NAME));
    00148         if (! auth_cookie.empty()) {
    00149             boost::mutex::scoped_lock cache_lock(m_cache_mutex);
    00150             PionUserCache::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         handleRedirection(http_request,tcp_conn,redirect_url,new_cookie,delete_cookie);
    00162     } else {
    00163         // otherwise - OK
    00164         handleOk(http_request,tcp_conn,new_cookie,delete_cookie);
    00165     }
    00166
    00167     // yes, we processed login/logout somehow
    00168     return true;
    00169 }
    00170
    00171 void HTTPCookieAuth::handleUnauthorized(HTTPRequestPtr& http_request,
    00172     TCPConnectionPtr& tcp_conn)
    00173 {
    00174     // if redirection option is used, send redirect
    00175     if (!m_redirect.empty()) {
    00176         handleRedirection(http_request,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     HTTPResponseWriterPtr writer(HTTPResponseWriter::create(tcp_conn, *http_request,
    00192     boost::bind(&TCPConnection::finish, tcp_conn)));
    00193     writer->getResponse().setStatusCode(HTTPTypes::RESPONSE_CODE_UNAUTHORIZED);
    00194     writer->getResponse().setStatusMessage(HTTPTypes::RESPONSE_MESSAGE_UNAUTHORIZED);
    00195     writer->writeNoCopy(CONTENT);
    00196     writer->send();
    00197 }
    00198
    00199 void HTTPCookieAuth::handleRedirection(HTTPRequestPtr& http_request,
    00200                                         TCPConnectionPtr& 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     HTTPResponseWriterPtr writer(HTTPResponseWriter::create(tcp_conn, *http_request,
    00218         boost::bind(&TCPConnection::finish, tcp_conn)));
    00219     writer->getResponse().setStatusCode(HTTPTypes::RESPONSE_CODE_FOUND);
    00220     writer->getResponse().setStatusMessage(HTTPTypes::RESPONSE_MESSAGE_FOUND);
    00221     writer->getResponse().addHeader(HTTPTypes::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->getResponse().deleteCookie(AUTH_COOKIE_NAME,"");
    00228     } else if (!new_cookie.empty()) {
    00229         // set up a new cookie
    00230         writer->getResponse().setCookie(AUTH_COOKIE_NAME, new_cookie,"");
    00231     }
    00232
    00233     writer->writeNoCopy(CONTENT);
    00234     writer->send();
    00235 }
    00236
    00237 void HTTPCookieAuth::handleOk(HTTPRequestPtr& http_request,
    00238                               TCPConnectionPtr& tcp_conn,
    00239                               const std::string &new_cookie,
    00240                               bool delete_cookie
    00241                               )
    00242 {
    00243     // send 204 (No Content) response
    00244     HTTPResponseWriterPtr writer(HTTPResponseWriter::create(tcp_conn, *http_request,
    00245         boost::bind(&TCPConnection::finish, tcp_conn)));
    00246     writer->getResponse().setStatusCode(HTTPTypes::RESPONSE_CODE_NO_CONTENT);
    00247     writer->getResponse().setStatusMessage(HTTPTypes::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->getResponse().deleteCookie(AUTH_COOKIE_NAME,"");
    00254     } else if(!new_cookie.empty()) {
    00255         // set up a new cookie
    00256         writer->getResponse().setCookie(AUTH_COOKIE_NAME, new_cookie,"");
    00257     }
    00258     writer->send();
    00259 }
    00260
    00261 void HTTPCookieAuth::expireCache(const PionDateTime &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         PionUserCache::iterator i;
    00267         PionUserCache::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 net
    00281 }   // end namespace pion