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