pion-net
4.0.9
|
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