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