xref: /openbmc/bmcweb/include/login_routes.hpp (revision 9d424669)
1 #pragma once
2 
3 #include <app.hpp>
4 #include <boost/container/flat_set.hpp>
5 #include <common.hpp>
6 #include <http_request.hpp>
7 #include <http_response.hpp>
8 #include <pam_authenticate.hpp>
9 #include <webassets.hpp>
10 
11 #include <random>
12 
13 namespace crow
14 {
15 
16 namespace login_routes
17 {
18 
19 inline void requestRoutes(App& app)
20 {
21     BMCWEB_ROUTE(app, "/login")
22         .methods(
23             boost::beast::http::verb::
24                 post)([](const crow::Request& req,
25                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
26             std::string_view contentType = req.getHeaderValue("content-type");
27             std::string_view username;
28             std::string_view password;
29 
30             bool looksLikePhosphorRest = false;
31 
32             // This object needs to be declared at this scope so the strings
33             // within it are not destroyed before we can use them
34             nlohmann::json loginCredentials;
35             // Check if auth was provided by a payload
36             if (boost::starts_with(contentType, "application/json"))
37             {
38                 loginCredentials =
39                     nlohmann::json::parse(req.body, nullptr, false);
40                 if (loginCredentials.is_discarded())
41                 {
42                     BMCWEB_LOG_DEBUG << "Bad json in request";
43                     asyncResp->res.result(
44                         boost::beast::http::status::bad_request);
45                     return;
46                 }
47 
48                 // check for username/password in the root object
49                 // THis method is how intel APIs authenticate
50                 nlohmann::json::iterator userIt =
51                     loginCredentials.find("username");
52                 nlohmann::json::iterator passIt =
53                     loginCredentials.find("password");
54                 if (userIt != loginCredentials.end() &&
55                     passIt != loginCredentials.end())
56                 {
57                     const std::string* userStr =
58                         userIt->get_ptr<const std::string*>();
59                     const std::string* passStr =
60                         passIt->get_ptr<const std::string*>();
61                     if (userStr != nullptr && passStr != nullptr)
62                     {
63                         username = *userStr;
64                         password = *passStr;
65                     }
66                 }
67                 else
68                 {
69                     // Openbmc appears to push a data object that contains the
70                     // same keys (username and password), attempt to use that
71                     auto dataIt = loginCredentials.find("data");
72                     if (dataIt != loginCredentials.end())
73                     {
74                         // Some apis produce an array of value ["username",
75                         // "password"]
76                         if (dataIt->is_array())
77                         {
78                             if (dataIt->size() == 2)
79                             {
80                                 nlohmann::json::iterator userIt2 =
81                                     dataIt->begin();
82                                 nlohmann::json::iterator passIt2 =
83                                     dataIt->begin() + 1;
84                                 looksLikePhosphorRest = true;
85                                 if (userIt2 != dataIt->end() &&
86                                     passIt2 != dataIt->end())
87                                 {
88                                     const std::string* userStr =
89                                         userIt2->get_ptr<const std::string*>();
90                                     const std::string* passStr =
91                                         passIt2->get_ptr<const std::string*>();
92                                     if (userStr != nullptr &&
93                                         passStr != nullptr)
94                                     {
95                                         username = *userStr;
96                                         password = *passStr;
97                                     }
98                                 }
99                             }
100                         }
101                         else if (dataIt->is_object())
102                         {
103                             nlohmann::json::iterator userIt2 =
104                                 dataIt->find("username");
105                             nlohmann::json::iterator passIt2 =
106                                 dataIt->find("password");
107                             if (userIt2 != dataIt->end() &&
108                                 passIt2 != dataIt->end())
109                             {
110                                 const std::string* userStr =
111                                     userIt2->get_ptr<const std::string*>();
112                                 const std::string* passStr =
113                                     passIt2->get_ptr<const std::string*>();
114                                 if (userStr != nullptr && passStr != nullptr)
115                                 {
116                                     username = *userStr;
117                                     password = *passStr;
118                                 }
119                             }
120                         }
121                     }
122                 }
123             }
124             else
125             {
126                 // check if auth was provided as a headers
127                 username = req.getHeaderValue("username");
128                 password = req.getHeaderValue("password");
129             }
130 
131             if (!username.empty() && !password.empty())
132             {
133                 int pamrc = pamAuthenticateUser(username, password);
134                 bool isConfigureSelfOnly = pamrc == PAM_NEW_AUTHTOK_REQD;
135                 if ((pamrc != PAM_SUCCESS) && !isConfigureSelfOnly)
136                 {
137                     asyncResp->res.result(
138                         boost::beast::http::status::unauthorized);
139                 }
140                 else
141                 {
142                     std::string unsupportedClientId = "";
143                     auto session =
144                         persistent_data::SessionStore::getInstance()
145                             .generateUserSession(
146                                 username, req.ipAddress.to_string(),
147                                 unsupportedClientId,
148                                 persistent_data::PersistenceType::TIMEOUT,
149                                 isConfigureSelfOnly);
150 
151                     if (looksLikePhosphorRest)
152                     {
153                         // Phosphor-Rest requires a very specific login
154                         // structure, and doesn't actually look at the status
155                         // code.
156                         // TODO(ed).... Fix that upstream
157                         asyncResp->res.jsonValue = {
158                             {"data",
159                              "User '" + std::string(username) + "' logged in"},
160                             {"message", "200 OK"},
161                             {"status", "ok"}};
162 
163                         // Hack alert.  Boost beast by default doesn't let you
164                         // declare multiple headers of the same name, and in
165                         // most cases this is fine.  Unfortunately here we need
166                         // to set the Session cookie, which requires the
167                         // httpOnly attribute, as well as the XSRF cookie, which
168                         // requires it to not have an httpOnly attribute. To get
169                         // the behavior we want, we simply inject the second
170                         // "set-cookie" string into the value header, and get
171                         // the result we want, even though we are technicaly
172                         // declaring two headers here.
173                         asyncResp->res.addHeader(
174                             "Set-Cookie",
175                             "XSRF-TOKEN=" + session->csrfToken +
176                                 "; SameSite=Strict; Secure\r\nSet-Cookie: "
177                                 "SESSION=" +
178                                 session->sessionToken +
179                                 "; SameSite=Strict; Secure; HttpOnly");
180                     }
181                     else
182                     {
183                         // if content type is json, assume json token
184                         asyncResp->res.jsonValue = {
185                             {"token", session->sessionToken}};
186                     }
187                 }
188             }
189             else
190             {
191                 BMCWEB_LOG_DEBUG << "Couldn't interpret password";
192                 asyncResp->res.result(boost::beast::http::status::bad_request);
193             }
194         });
195 
196     BMCWEB_ROUTE(app, "/logout")
197         .methods(boost::beast::http::verb::post)(
198             [](const crow::Request& req,
199                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
200                 auto& session = req.session;
201                 if (session != nullptr)
202                 {
203                     asyncResp->res.jsonValue = {
204                         {"data", "User '" + session->username + "' logged out"},
205                         {"message", "200 OK"},
206                         {"status", "ok"}};
207 
208                     persistent_data::SessionStore::getInstance().removeSession(
209                         session);
210                 }
211                 return;
212             });
213 }
214 } // namespace login_routes
215 } // namespace crow
216