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
performBasicAuth(const boost::asio::ip::address & clientIp,std::string_view authHeader)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>
performTokenAuth(std::string_view authHeader)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>
performXtokenAuth(const boost::beast::http::header<true> & reqHeader)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>
performCookieAuth(boost::beast::http::verb method,const boost::beast::http::header<true> & reqHeader)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
performTLSAuth(Response & res,const std::shared_ptr<persistent_data::UserSession> & session)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
isOnAllowlist(std::string_view url,boost::beast::http::verb method)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
authenticate(const boost::asio::ip::address & ipAddress,Response & res,boost::beast::http::verb method,const boost::beast::http::header<true> & reqHeader,const std::shared_ptr<persistent_data::UserSession> & session)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