xref: /openbmc/bmcweb/include/login_routes.hpp (revision 636be396)
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                     std::string unsupportedClientId = "";
140                     auto session =
141                         persistent_data::SessionStore::getInstance()
142                             .generateUserSession(
143                                 username, req.ipAddress.to_string(),
144                                 unsupportedClientId,
145                                 persistent_data::PersistenceType::TIMEOUT,
146                                 isConfigureSelfOnly);
147 
148                     if (looksLikePhosphorRest)
149                     {
150                         // Phosphor-Rest requires a very specific login
151                         // structure, and doesn't actually look at the status
152                         // code.
153                         // TODO(ed).... Fix that upstream
154                         res.jsonValue = {
155                             {"data",
156                              "User '" + std::string(username) + "' logged in"},
157                             {"message", "200 OK"},
158                             {"status", "ok"}};
159 
160                         // Hack alert.  Boost beast by default doesn't let you
161                         // declare multiple headers of the same name, and in
162                         // most cases this is fine.  Unfortunately here we need
163                         // to set the Session cookie, which requires the
164                         // httpOnly attribute, as well as the XSRF cookie, which
165                         // requires it to not have an httpOnly attribute. To get
166                         // the behavior we want, we simply inject the second
167                         // "set-cookie" string into the value header, and get
168                         // the result we want, even though we are technicaly
169                         // declaring two headers here.
170                         res.addHeader(
171                             "Set-Cookie",
172                             "XSRF-TOKEN=" + session->csrfToken +
173                                 "; SameSite=Strict; Secure\r\nSet-Cookie: "
174                                 "SESSION=" +
175                                 session->sessionToken +
176                                 "; SameSite=Strict; Secure; HttpOnly");
177                     }
178                     else
179                     {
180                         // if content type is json, assume json token
181                         res.jsonValue = {{"token", session->sessionToken}};
182                     }
183                 }
184             }
185             else
186             {
187                 BMCWEB_LOG_DEBUG << "Couldn't interpret password";
188                 res.result(boost::beast::http::status::bad_request);
189             }
190             res.end();
191         });
192 
193     BMCWEB_ROUTE(app, "/logout")
194         .methods(boost::beast::http::verb::post)(
195             [](const crow::Request& req, crow::Response& res) {
196                 auto& session = req.session;
197                 if (session != nullptr)
198                 {
199                     res.jsonValue = {
200                         {"data", "User '" + session->username + "' logged out"},
201                         {"message", "200 OK"},
202                         {"status", "ok"}};
203 
204                     persistent_data::SessionStore::getInstance().removeSession(
205                         session);
206                 }
207                 res.end();
208                 return;
209             });
210 }
211 } // namespace login_routes
212 } // namespace crow
213