xref: /openbmc/bmcweb/features/redfish/lib/redfish_sessions.hpp (revision 43a095ab36b35bcb9192218fd11e0ff86ec63bf9)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "node.hpp"
19 #include "session_storage_singleton.hpp"
20 
21 namespace redfish {
22 
23 static OperationMap sessionOpMap = {
24     {crow::HTTPMethod::GET, {{"Login"}}},
25     {crow::HTTPMethod::HEAD, {{"Login"}}},
26     {crow::HTTPMethod::PATCH, {{"ConfigureManager"}}},
27     {crow::HTTPMethod::PUT, {{"ConfigureManager"}}},
28     {crow::HTTPMethod::DELETE, {{"ConfigureManager"}}},
29     {crow::HTTPMethod::POST, {{"ConfigureManager"}}}};
30 
31 static OperationMap sessionCollectionOpMap = {
32     {crow::HTTPMethod::GET, {{"Login"}}},
33     {crow::HTTPMethod::HEAD, {{"Login"}}},
34     {crow::HTTPMethod::PATCH, {{"ConfigureManager"}}},
35     {crow::HTTPMethod::PUT, {{"ConfigureManager"}}},
36     {crow::HTTPMethod::DELETE, {{"ConfigureManager"}}},
37     {crow::HTTPMethod::POST, {{}}}};
38 
39 class SessionCollection;
40 
41 class Sessions : public Node {
42  public:
43   template <typename CrowApp>
44   Sessions(CrowApp& app)
45       : Node(app, EntityPrivileges(std::move(sessionOpMap)),
46              "/redfish/v1/SessionService/Sessions/<str>", std::string()) {
47     nodeJson["@odata.type"] = "#Session.v1_0_2.Session";
48     nodeJson["@odata.context"] = "/redfish/v1/$metadata#Session.Session";
49     nodeJson["Name"] = "User Session";
50     nodeJson["Description"] = "Manager User Session";
51   }
52 
53  private:
54   void doGet(crow::response& res, const crow::request& req,
55              const std::vector<std::string>& params) override {
56     auto session =
57         crow::PersistentData::session_store->get_session_by_uid(params[0]);
58 
59     if (session == nullptr) {
60       res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
61       res.end();
62       return;
63     }
64 
65     nodeJson["Id"] = session->unique_id;
66     nodeJson["UserName"] = session->username;
67     nodeJson["@odata.id"] =
68         "/redfish/v1/SessionService/Sessions/" + session->unique_id;
69 
70     res.json_value = nodeJson;
71     res.end();
72   }
73 
74   void doDelete(crow::response& res, const crow::request& req,
75                 const std::vector<std::string>& params) override {
76     // Need only 1 param which should be id of session to be deleted
77     if (params.size() != 1) {
78       res.code = static_cast<int>(HttpRespCode::BAD_REQUEST);
79       res.end();
80       return;
81     }
82 
83     auto session =
84         crow::PersistentData::session_store->get_session_by_uid(params[0]);
85 
86     if (session == nullptr) {
87       res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
88       res.end();
89       return;
90     }
91 
92     crow::PersistentData::session_store->remove_session(session);
93     res.code = static_cast<int>(HttpRespCode::OK);
94     res.end();
95   }
96 
97   /**
98    * This allows SessionCollection to reuse this class' doGet method, to
99    * maintain consistency of returned data, as Collection's doPost should return
100    * data for created member which should match member's doGet result in 100%
101    */
102   friend SessionCollection;
103 
104   nlohmann::json nodeJson;
105 };
106 
107 class SessionCollection : public Node {
108  public:
109   template <typename CrowApp>
110   SessionCollection(CrowApp& app)
111       : Node(app, EntityPrivileges(std::move(sessionCollectionOpMap)),
112              "/redfish/v1/SessionService/Sessions/"),
113         memberSession(app) {
114     nodeJson["@odata.type"] = "#SessionCollection.SessionCollection";
115     nodeJson["@odata.id"] = "/redfish/v1/SessionService/Sessions/";
116     nodeJson["@odata.context"] =
117         "/redfish/v1/$metadata#SessionCollection.SessionCollection";
118     nodeJson["Name"] = "Session Collection";
119     nodeJson["Description"] = "Session Collection";
120     nodeJson["Members@odata.count"] = 0;
121     nodeJson["Members"] = nlohmann::json::array();
122   }
123 
124  private:
125   void doGet(crow::response& res, const crow::request& req,
126              const std::vector<std::string>& params) override {
127     std::vector<const std::string*> session_ids =
128         crow::PersistentData::session_store->get_unique_ids(
129             false, crow::PersistentData::PersistenceType::TIMEOUT);
130 
131     nodeJson["Members@odata.count"] = session_ids.size();
132     nodeJson["Members"] = nlohmann::json::array();
133     for (const auto& uid : session_ids) {
134       nodeJson["Members"].push_back(
135           {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
136     }
137 
138     res.json_value = nodeJson;
139     res.end();
140   }
141 
142   void doPost(crow::response& res, const crow::request& req,
143               const std::vector<std::string>& params) override {
144     std::string username;
145     bool userAuthSuccessful = authenticateUser(req, &res.code, &username);
146     if (!userAuthSuccessful) {
147       res.end();
148       return;
149     }
150 
151     // User is authenticated - create session for him
152     auto session =
153         crow::PersistentData::session_store->generate_user_session(username);
154     res.add_header("X-Auth-Token", session.session_token);
155 
156     // Return data for created session
157     memberSession.doGet(res, req, {session.unique_id});
158 
159     // No need for res.end(), as it is called by doGet()
160   }
161 
162   /**
163    * @brief Verifies data provided in request and tries to authenticate user
164    *
165    * @param[in]  req            Crow request containing authentication data
166    * @param[out] httpRespCode   HTTP Code that should be returned in response
167    * @param[out] user           Retrieved username - not filled on failure
168    *
169    * @return true if authentication was successful, false otherwise
170    */
171   bool authenticateUser(const crow::request& req, int* httpRespCode,
172                         std::string* user) {
173     // We need only UserName and Password - nothing more, nothing less
174     static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
175 
176     // call with exceptions disabled
177     auto login_credentials = nlohmann::json::parse(req.body, nullptr, false);
178     if (login_credentials.is_discarded()) {
179       *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
180 
181       return false;
182     }
183 
184     // Check that there are only as many fields as there should be
185     if (login_credentials.size() != numberOfRequiredFieldsInReq) {
186       *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
187 
188       return false;
189     }
190 
191     // Find fields that we need - UserName and Password
192     auto user_it = login_credentials.find("UserName");
193     auto pass_it = login_credentials.find("Password");
194     if (user_it == login_credentials.end() ||
195         pass_it == login_credentials.end()) {
196       *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
197 
198       return false;
199     }
200 
201     // Check that given data is of valid type (string)
202     if (!user_it->is_string() || !pass_it->is_string()) {
203       *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
204 
205       return false;
206     }
207 
208     // Extract username and password
209     std::string username = user_it->get<const std::string>();
210     std::string password = pass_it->get<const std::string>();
211 
212     // Verify that required fields are not empty
213     if (username.empty() || password.empty()) {
214       *httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
215 
216       return false;
217     }
218 
219     // Finally - try to authenticate user
220     if (!pam_authenticate_user(username, password)) {
221       *httpRespCode = static_cast<int>(HttpRespCode::UNAUTHORIZED);
222 
223       return false;
224     }
225 
226     // User authenticated successfully
227     *httpRespCode = static_cast<int>(HttpRespCode::OK);
228     *user = username;
229 
230     return true;
231   }
232 
233   /**
234    * Member session to ensure consistency between collection's doPost and
235    * member's doGet, as they should return 100% matching data
236    */
237   Sessions memberSession;
238   nlohmann::json nodeJson;
239 };
240 
241 }  // namespace redfish
242