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