xref: /openbmc/bmcweb/include/authentication.hpp (revision 3ce3688a)
1 #pragma once
2 
3 #include "cookies.hpp"
4 #include "forward_unauthorized.hpp"
5 #include "http_request.hpp"
6 #include "http_response.hpp"
7 #include "http_utility.hpp"
8 #include "pam_authenticate.hpp"
9 #include "webroutes.hpp"
10 
11 #include <boost/container/flat_set.hpp>
12 
13 #include <random>
14 #include <utility>
15 
16 namespace crow
17 {
18 
19 namespace authentication
20 {
21 
22 inline void cleanupTempSession(const Request& req)
23 {
24     // TODO(ed) THis should really be handled by the persistent data
25     // middleware, but because it is upstream, it doesn't have access to the
26     // session information.  Should the data middleware persist the current
27     // user session?
28     if (req.session != nullptr &&
29         req.session->persistence ==
30             persistent_data::PersistenceType::SINGLE_REQUEST)
31     {
32         persistent_data::SessionStore::getInstance().removeSession(req.session);
33     }
34 }
35 
36 inline std::shared_ptr<persistent_data::UserSession>
37     performBasicAuth(const boost::asio::ip::address& clientIp,
38                      std::string_view authHeader)
39 {
40     BMCWEB_LOG_DEBUG("[AuthMiddleware] Basic authentication");
41 
42     if (!authHeader.starts_with("Basic "))
43     {
44         return nullptr;
45     }
46 
47     std::string_view param = authHeader.substr(strlen("Basic "));
48     std::string authData;
49 
50     if (!crow::utility::base64Decode(param, authData))
51     {
52         return nullptr;
53     }
54     std::size_t separator = authData.find(':');
55     if (separator == std::string::npos)
56     {
57         return nullptr;
58     }
59 
60     std::string user = authData.substr(0, separator);
61     separator += 1;
62     if (separator > authData.size())
63     {
64         return nullptr;
65     }
66     std::string pass = authData.substr(separator);
67 
68     BMCWEB_LOG_DEBUG("[AuthMiddleware] Authenticating user: {}", user);
69     BMCWEB_LOG_DEBUG("[AuthMiddleware] User IPAddress: {}",
70                      clientIp.to_string());
71 
72     int pamrc = pamAuthenticateUser(user, pass);
73     bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
74     if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
75     {
76         return nullptr;
77     }
78 
79     // TODO(ed) generateUserSession is a little expensive for basic
80     // auth, as it generates some random identifiers that will never be
81     // used.  This should have a "fast" path for when user tokens aren't
82     // needed.
83     // This whole flow needs to be revisited anyway, as we can't be
84     // calling directly into pam for every request
85     return persistent_data::SessionStore::getInstance().generateUserSession(
86         user, clientIp, std::nullopt,
87         persistent_data::PersistenceType::SINGLE_REQUEST, isConfigureSelfOnly);
88 }
89 
90 inline std::shared_ptr<persistent_data::UserSession>
91     performTokenAuth(std::string_view authHeader)
92 {
93     BMCWEB_LOG_DEBUG("[AuthMiddleware] Token authentication");
94     if (!authHeader.starts_with("Token "))
95     {
96         return nullptr;
97     }
98     std::string_view token = authHeader.substr(strlen("Token "));
99     auto sessionOut =
100         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
101     return sessionOut;
102 }
103 
104 inline std::shared_ptr<persistent_data::UserSession>
105     performXtokenAuth(const boost::beast::http::header<true>& reqHeader)
106 {
107     BMCWEB_LOG_DEBUG("[AuthMiddleware] X-Auth-Token authentication");
108 
109     std::string_view token = reqHeader["X-Auth-Token"];
110     if (token.empty())
111     {
112         return nullptr;
113     }
114     auto sessionOut =
115         persistent_data::SessionStore::getInstance().loginSessionByToken(token);
116     return sessionOut;
117 }
118 
119 inline std::shared_ptr<persistent_data::UserSession>
120     performCookieAuth(boost::beast::http::verb method [[maybe_unused]],
121                       const boost::beast::http::header<true>& reqHeader)
122 {
123     using headers = boost::beast::http::header<true>;
124     std::pair<headers::const_iterator, headers::const_iterator> cookies =
125         reqHeader.equal_range(boost::beast::http::field::cookie);
126 
127     for (auto it = cookies.first; it != cookies.second; it++)
128     {
129         std::string_view cookieValue = it->value();
130         BMCWEB_LOG_DEBUG("Checking cookie {}", cookieValue);
131         auto startIndex = cookieValue.find("SESSION=");
132         if (startIndex == std::string::npos)
133         {
134             BMCWEB_LOG_DEBUG(
135                 "Cookie was present, but didn't look like a session {}",
136                 cookieValue);
137             continue;
138         }
139         startIndex += sizeof("SESSION=") - 1;
140         auto endIndex = cookieValue.find(';', startIndex);
141         if (endIndex == std::string::npos)
142         {
143             endIndex = cookieValue.size();
144         }
145         std::string_view authKey = cookieValue.substr(startIndex,
146                                                       endIndex - startIndex);
147 
148         std::shared_ptr<persistent_data::UserSession> sessionOut =
149             persistent_data::SessionStore::getInstance().loginSessionByToken(
150                 authKey);
151         if (sessionOut == nullptr)
152         {
153             return nullptr;
154         }
155         sessionOut->cookieAuth = true;
156 
157         if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF)
158         {
159             // RFC7231 defines methods that need csrf protection
160             if (method != boost::beast::http::verb::get)
161             {
162                 std::string_view csrf = reqHeader["X-XSRF-TOKEN"];
163                 // Make sure both tokens are filled
164                 if (csrf.empty() || sessionOut->csrfToken.empty())
165                 {
166                     return nullptr;
167                 }
168 
169                 if (csrf.size() != persistent_data::sessionTokenSize)
170                 {
171                     return nullptr;
172                 }
173                 // Reject if csrf token not available
174                 if (!crow::utility::constantTimeStringCompare(
175                         csrf, sessionOut->csrfToken))
176                 {
177                     return nullptr;
178                 }
179             }
180         }
181         return sessionOut;
182     }
183     return nullptr;
184 }
185 
186 inline std::shared_ptr<persistent_data::UserSession>
187     performTLSAuth(Response& res,
188                    const boost::beast::http::header<true>& reqHeader,
189                    const std::weak_ptr<persistent_data::UserSession>& session)
190 {
191     if (auto sp = session.lock())
192     {
193         // set cookie only if this is req from the browser.
194         if (reqHeader["User-Agent"].empty())
195         {
196             BMCWEB_LOG_DEBUG(" TLS session: {} will be used for this request.",
197                              sp->uniqueId);
198             return sp;
199         }
200         // TODO: change this to not switch to cookie auth
201         bmcweb::setSessionCookies(res, *sp);
202         res.addHeader(boost::beast::http::field::set_cookie,
203                       "IsAuthenticated=true; Secure");
204         BMCWEB_LOG_DEBUG(
205             " TLS session: {} with cookie will be used for this request.",
206             sp->uniqueId);
207         return sp;
208     }
209     return nullptr;
210 }
211 
212 // checks if request can be forwarded without authentication
213 inline bool isOnAllowlist(std::string_view url, boost::beast::http::verb method)
214 {
215     // Handle the case where the router registers routes as both ending with /
216     // and not.
217     if (url.ends_with('/') && url != "/")
218     {
219         url.remove_suffix(1);
220     }
221     if (boost::beast::http::verb::get == method)
222     {
223         if ((url == "/redfish") ||          //
224             (url == "/redfish/v1") ||       //
225             (url == "/redfish/v1/odata") || //
226             (url == "/redfish/v1/$metadata"))
227         {
228             return true;
229         }
230         if (crow::webroutes::routes.find(std::string(url)) !=
231             crow::webroutes::routes.end())
232         {
233             return true;
234         }
235     }
236 
237     // it's allowed to POST on session collection & login without
238     // authentication
239     if (boost::beast::http::verb::post == method)
240     {
241         if ((url == "/redfish/v1/SessionService/Sessions") ||
242             (url == "/redfish/v1/SessionService/Sessions/Members") ||
243             (url == "/login"))
244         {
245             return true;
246         }
247     }
248 
249     return false;
250 }
251 
252 inline std::shared_ptr<persistent_data::UserSession> authenticate(
253     const boost::asio::ip::address& ipAddress [[maybe_unused]],
254     Response& res [[maybe_unused]],
255     boost::beast::http::verb method [[maybe_unused]],
256     const boost::beast::http::header<true>& reqHeader,
257     [[maybe_unused]] const std::shared_ptr<persistent_data::UserSession>&
258         session)
259 {
260     const persistent_data::AuthConfigMethods& authMethodsConfig =
261         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
262 
263     std::shared_ptr<persistent_data::UserSession> sessionOut = nullptr;
264     if constexpr (BMCWEB_MUTUAL_TLS_AUTH)
265     {
266         if (authMethodsConfig.tls)
267         {
268             sessionOut = performTLSAuth(res, reqHeader, session);
269         }
270     }
271     if constexpr (BMCWEB_XTOKEN_AUTH)
272     {
273         if (sessionOut == nullptr && authMethodsConfig.xtoken)
274         {
275             sessionOut = performXtokenAuth(reqHeader);
276         }
277     }
278     if constexpr (BMCWEB_COOKIE_AUTH)
279     {
280         if (sessionOut == nullptr && authMethodsConfig.cookie)
281         {
282             sessionOut = performCookieAuth(method, reqHeader);
283         }
284     }
285     std::string_view authHeader = reqHeader["Authorization"];
286     BMCWEB_LOG_DEBUG("authHeader={}", authHeader);
287     if constexpr (BMCWEB_SESSION_AUTH)
288     {
289         if (sessionOut == nullptr && authMethodsConfig.sessionToken)
290         {
291             sessionOut = performTokenAuth(authHeader);
292         }
293     }
294     if constexpr (BMCWEB_BASIC_AUTH)
295     {
296         if (sessionOut == nullptr && authMethodsConfig.basic)
297         {
298             sessionOut = performBasicAuth(ipAddress, authHeader);
299         }
300     }
301     if (sessionOut != nullptr)
302     {
303         return sessionOut;
304     }
305 
306     return nullptr;
307 }
308 
309 } // namespace authentication
310 } // namespace crow
311