00001
00002
00003
00004
00005
00006
00007
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 {
00018 namespace net {
00019
00020
00021
00022
00023 const unsigned int HTTPCookieAuth::CACHE_EXPIRATION = 3600;
00024 const unsigned int HTTPCookieAuth::RANDOM_COOKIE_BYTES = 20;
00025 const std::string HTTPCookieAuth::AUTH_COOKIE_NAME = "pion_session_id";
00026
00027
00028
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
00039 setLogger(PION_GET_LOGGER("pion.net.HTTPCookieAuth"));
00040
00041
00042
00043
00044
00045 m_random_gen.seed(static_cast<boost::mt19937::result_type>(::time(NULL)));
00046
00047
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;
00056 }
00057
00058 if (!needAuthentication(request)) {
00059 return true;
00060 }
00061
00062
00063 if (!m_redirect.empty() && m_redirect==request->getResource()) {
00064 return true;
00065 }
00066
00067
00068 PionDateTime time_now(boost::posix_time::second_clock::universal_time());
00069 expireCache(time_now);
00070
00071
00072 const std::string auth_cookie(request->getCookie(AUTH_COOKIE_NAME));
00073 if (! auth_cookie.empty()) {
00074
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
00079
00080 request->setUser(user_cache_itr->second.second);
00081
00082 user_cache_itr->second.first = time_now;
00083 return true;
00084 }
00085 }
00086
00087
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
00107 std::string resource(HTTPServer::stripTrailingSlash(http_request->getResource()));
00108
00109 if (resource != m_login && resource != m_logout) {
00110 return false;
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
00119
00120 std::string username = HTTPTypes::url_decode(http_request->getQuery("user"));
00121 std::string password = HTTPTypes::url_decode(http_request->getQuery("pass"));
00122
00123
00124 PionUserPtr user=m_user_manager->getUser(username,password);
00125 if (!user) {
00126 handleUnauthorized(http_request,tcp_conn);
00127 return true;
00128 }
00129
00130
00131
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
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
00145
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
00155 delete_cookie = true;
00156 }
00157
00158
00159 if (! redirect_url.empty()) {
00160 handleRedirection(http_request,tcp_conn,redirect_url,new_cookie,delete_cookie);
00161 } else {
00162
00163 handleOk(http_request,tcp_conn,new_cookie,delete_cookie);
00164 }
00165
00166
00167 return true;
00168 }
00169
00170 void HTTPCookieAuth::handleUnauthorized(HTTPRequestPtr& http_request,
00171 TCPConnectionPtr& tcp_conn)
00172 {
00173
00174 if (!m_redirect.empty()) {
00175 handleRedirection(http_request,tcp_conn,m_redirect,"",false);
00176 return;
00177 }
00178
00179
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
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
00222
00223
00224 if (delete_cookie) {
00225
00226 writer->getResponse().deleteCookie(AUTH_COOKIE_NAME,"");
00227 } else if (!new_cookie.empty()) {
00228
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
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
00248
00249
00250 if (delete_cookie) {
00251
00252 writer->getResponse().deleteCookie(AUTH_COOKIE_NAME,"");
00253 } else if(!new_cookie.empty()) {
00254
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
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
00272 m_user_cache.erase(i);
00273 }
00274 }
00275 m_cache_cleanup_time = time_now;
00276 }
00277 }
00278
00279 }
00280 }