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

Generated on Fri Apr 30 14:48:53 2010 for pion-net by  doxygen 1.4.7