blob: 2406250fa381d278127dd0aa91e74c60d08c8c83 [file] [log] [blame]
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +01001/*
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
Borawski.Lukasz43a095a2018-02-19 15:39:01 +010017
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010018#include "error_messages.hpp"
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010019#include "node.hpp"
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020020#include "persistent_data_middleware.hpp"
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010021
Ed Tanous1abe55e2018-09-05 08:30:59 -070022namespace redfish
23{
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010024
25class SessionCollection;
26
Ed Tanous1abe55e2018-09-05 08:30:59 -070027class Sessions : public Node
28{
29 public:
30 Sessions(CrowApp& app) :
31 Node(app, "/redfish/v1/SessionService/Sessions/<str>/", std::string())
32 {
33 Node::json["@odata.type"] = "#Session.v1_0_2.Session";
34 Node::json["@odata.context"] = "/redfish/v1/$metadata#Session.Session";
35 Node::json["Name"] = "User Session";
36 Node::json["Description"] = "Manager User Session";
Ed Tanous3ebd75f2018-03-05 18:20:01 -080037
Ed Tanous1abe55e2018-09-05 08:30:59 -070038 entityPrivileges = {
39 {boost::beast::http::verb::get, {{"Login"}}},
40 {boost::beast::http::verb::head, {{"Login"}}},
41 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
42 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
43 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
44 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010045 }
46
Ed Tanous1abe55e2018-09-05 08:30:59 -070047 private:
48 void doGet(crow::Response& res, const crow::Request& req,
49 const std::vector<std::string>& params) override
50 {
51 auto session =
52 crow::persistent_data::SessionStore::getInstance().getSessionByUid(
53 params[0]);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010054
Ed Tanous1abe55e2018-09-05 08:30:59 -070055 if (session == nullptr)
56 {
57 messages::addMessageToErrorJson(
58 res.jsonValue,
59 messages::resourceNotFound("Session", params[0]));
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010060
Ed Tanous1abe55e2018-09-05 08:30:59 -070061 res.result(boost::beast::http::status::not_found);
62 res.end();
63 return;
64 }
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010065
Ed Tanous1abe55e2018-09-05 08:30:59 -070066 Node::json["Id"] = session->uniqueId;
67 Node::json["UserName"] = session->username;
68 Node::json["@odata.id"] =
69 "/redfish/v1/SessionService/Sessions/" + session->uniqueId;
Ed Tanouse0d918b2018-03-27 17:41:04 -070070
Ed Tanous1abe55e2018-09-05 08:30:59 -070071 res.jsonValue = Node::json;
72 res.end();
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010073 }
74
Ed Tanous1abe55e2018-09-05 08:30:59 -070075 void doDelete(crow::Response& res, const crow::Request& req,
76 const std::vector<std::string>& params) override
77 {
78 // Need only 1 param which should be id of session to be deleted
79 if (params.size() != 1)
80 {
81 // This should be handled by crow and never happen
82 BMCWEB_LOG_ERROR << "Session DELETE has been called with invalid "
83 "number of params";
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010084
Ed Tanous1abe55e2018-09-05 08:30:59 -070085 res.result(boost::beast::http::status::bad_request);
86 messages::addMessageToErrorJson(res.jsonValue,
87 messages::generalError());
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010088
Ed Tanous1abe55e2018-09-05 08:30:59 -070089 res.end();
90 return;
91 }
92
93 auto session =
94 crow::persistent_data::SessionStore::getInstance().getSessionByUid(
95 params[0]);
96
97 if (session == nullptr)
98 {
99 messages::addMessageToErrorJson(
100 res.jsonValue,
101 messages::resourceNotFound("Session", params[0]));
102
103 res.result(boost::beast::http::status::not_found);
104 res.end();
105 return;
106 }
107
108 // DELETE should return representation of object that will be removed
109 doGet(res, req, params);
110
111 crow::persistent_data::SessionStore::getInstance().removeSession(
112 session);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100113 }
114
Ed Tanous1abe55e2018-09-05 08:30:59 -0700115 /**
116 * This allows SessionCollection to reuse this class' doGet method, to
117 * maintain consistency of returned data, as Collection's doPost should
118 * return data for created member which should match member's doGet result
119 * in 100%
120 */
121 friend SessionCollection;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100122};
123
Ed Tanous1abe55e2018-09-05 08:30:59 -0700124class SessionCollection : public Node
125{
126 public:
127 SessionCollection(CrowApp& app) :
128 Node(app, "/redfish/v1/SessionService/Sessions/"), memberSession(app)
129 {
130 Node::json["@odata.type"] = "#SessionCollection.SessionCollection";
131 Node::json["@odata.id"] = "/redfish/v1/SessionService/Sessions/";
132 Node::json["@odata.context"] =
133 "/redfish/v1/$metadata#SessionCollection.SessionCollection";
134 Node::json["Name"] = "Session Collection";
135 Node::json["Description"] = "Session Collection";
136 Node::json["Members@odata.count"] = 0;
137 Node::json["Members"] = nlohmann::json::array();
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800138
Ed Tanous1abe55e2018-09-05 08:30:59 -0700139 entityPrivileges = {
140 {boost::beast::http::verb::get, {{"Login"}}},
141 {boost::beast::http::verb::head, {{"Login"}}},
142 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
143 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
144 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
145 {boost::beast::http::verb::post, {}}};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100146 }
147
Ed Tanous1abe55e2018-09-05 08:30:59 -0700148 private:
149 void doGet(crow::Response& res, const crow::Request& req,
150 const std::vector<std::string>& params) override
151 {
152 std::vector<const std::string*> sessionIds =
153 crow::persistent_data::SessionStore::getInstance().getUniqueIds(
154 false, crow::persistent_data::PersistenceType::TIMEOUT);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100155
Ed Tanous1abe55e2018-09-05 08:30:59 -0700156 Node::json["Members@odata.count"] = sessionIds.size();
157 Node::json["Members"] = nlohmann::json::array();
158 for (const std::string* uid : sessionIds)
159 {
160 Node::json["Members"].push_back(
161 {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
162 }
Ed Tanouse0d918b2018-03-27 17:41:04 -0700163
Ed Tanous1abe55e2018-09-05 08:30:59 -0700164 res.jsonValue = Node::json;
165 res.end();
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100166 }
167
Ed Tanous1abe55e2018-09-05 08:30:59 -0700168 void doPost(crow::Response& res, const crow::Request& req,
169 const std::vector<std::string>& params) override
170 {
171 boost::beast::http::status status;
172 std::string username;
173 bool userAuthSuccessful =
174 authenticateUser(req, status, username, res.jsonValue);
175 res.result(status);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100176
Ed Tanous1abe55e2018-09-05 08:30:59 -0700177 if (!userAuthSuccessful)
178 {
179 res.end();
180 return;
181 }
Ed Tanousb9845d92018-07-24 14:38:06 -0700182
Ed Tanous1abe55e2018-09-05 08:30:59 -0700183 // User is authenticated - create session for him
184 auto session = crow::persistent_data::SessionStore::getInstance()
185 .generateUserSession(username);
186 res.addHeader("X-Auth-Token", session->sessionToken);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100187
Ed Tanous1abe55e2018-09-05 08:30:59 -0700188 res.addHeader("Location", "/redfish/v1/SessionService/Sessions/" +
189 session->uniqueId);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100190
Ed Tanous1abe55e2018-09-05 08:30:59 -0700191 // Return data for created session
192 memberSession.doGet(res, req, {session->uniqueId});
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100193
Ed Tanous1abe55e2018-09-05 08:30:59 -0700194 // No need for res.end(), as it is called by doGet()
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100195 }
196
Ed Tanous1abe55e2018-09-05 08:30:59 -0700197 /**
198 * @brief Verifies data provided in request and tries to authenticate user
199 *
200 * @param[in] req Crow request containing authentication data
201 * @param[out] httpRespCode HTTP Code that should be returned in response
202 * @param[out] user Retrieved username - not filled on failure
203 * @param[out] errJson JSON to which error messages will be written
204 *
205 * @return true if authentication was successful, false otherwise
206 */
207 bool authenticateUser(const crow::Request& req,
208 boost::beast::http::status& httpRespCode,
209 std::string& user, nlohmann::json& errJson)
210 {
211 // We need only UserName and Password - nothing more, nothing less
212 static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100213
Ed Tanous1abe55e2018-09-05 08:30:59 -0700214 // call with exceptions disabled
215 auto loginCredentials = nlohmann::json::parse(req.body, nullptr, false);
216 if (loginCredentials.is_discarded())
217 {
218 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100219
Ed Tanous1abe55e2018-09-05 08:30:59 -0700220 messages::addMessageToErrorJson(errJson, messages::malformedJSON());
221
222 return false;
223 }
224
225 // Check that there are only as many fields as there should be
226 if (loginCredentials.size() != numberOfRequiredFieldsInReq)
227 {
228 httpRespCode = boost::beast::http::status::bad_request;
229
230 messages::addMessageToErrorJson(errJson, messages::malformedJSON());
231
232 return false;
233 }
234
235 // Find fields that we need - UserName and Password
236 auto userIt = loginCredentials.find("UserName");
237 auto passIt = loginCredentials.find("Password");
238 if (userIt == loginCredentials.end() ||
239 passIt == loginCredentials.end())
240 {
241 httpRespCode = boost::beast::http::status::bad_request;
242
243 if (userIt == loginCredentials.end())
244 {
245 messages::addMessageToErrorJson(
246 errJson, messages::propertyMissing("UserName"));
247 }
248
249 if (passIt == loginCredentials.end())
250 {
251 messages::addMessageToErrorJson(
252 errJson, messages::propertyMissing("Password"));
253 }
254
255 return false;
256 }
257
258 // Check that given data is of valid type (string)
259 if (!userIt->is_string() || !passIt->is_string())
260 {
261 httpRespCode = boost::beast::http::status::bad_request;
262
263 if (!userIt->is_string())
264 {
265 messages::addMessageToErrorJson(
266 errJson, messages::propertyValueTypeError(userIt->dump(),
267 "UserName"));
268 }
269
270 if (!passIt->is_string())
271 {
272 messages::addMessageToErrorJson(
273 errJson, messages::propertyValueTypeError(userIt->dump(),
274 "Password"));
275 }
276
277 return false;
278 }
279
280 // Extract username and password
281 std::string username = userIt->get<const std::string>();
282 std::string password = passIt->get<const std::string>();
283
284 // Verify that required fields are not empty
285 if (username.empty() || password.empty())
286 {
287 httpRespCode = boost::beast::http::status::bad_request;
288
289 if (username.empty())
290 {
291 messages::addMessageToErrorJson(
292 errJson, messages::propertyMissing("UserName"));
293 }
294
295 if (password.empty())
296 {
297 messages::addMessageToErrorJson(
298 errJson, messages::propertyMissing("Password"));
299 }
300
301 return false;
302 }
303
304 // Finally - try to authenticate user
305 if (!pamAuthenticateUser(username, password))
306 {
307 httpRespCode = boost::beast::http::status::unauthorized;
308
309 messages::addMessageToErrorJson(
310 errJson,
311 messages::resourceAtUriUnauthorized(
312 std::string(req.url), "Invalid username or password"));
313
314 return false;
315 }
316
317 // User authenticated successfully
318 httpRespCode = boost::beast::http::status::ok;
319 user = username;
320
321 return true;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100322 }
323
Ed Tanous1abe55e2018-09-05 08:30:59 -0700324 /**
325 * Member session to ensure consistency between collection's doPost and
326 * member's doGet, as they should return 100% matching data
327 */
328 Sessions memberSession;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100329};
330
Ed Tanous1abe55e2018-09-05 08:30:59 -0700331class SessionService : public Node
332{
333 public:
334 SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/")
335 {
336 Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
337 Node::json["@odata.id"] = "/redfish/v1/SessionService/";
338 Node::json["@odata.context"] =
339 "/redfish/v1/$metadata#SessionService.SessionService";
340 Node::json["Name"] = "Session Service";
341 Node::json["Id"] = "SessionService";
342 Node::json["Description"] = "Session Service";
343 Node::json["SessionTimeout"] =
344 crow::persistent_data::SessionStore::getInstance()
345 .getTimeoutInSeconds();
346 Node::json["ServiceEnabled"] = true;
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800347
Ed Tanous1abe55e2018-09-05 08:30:59 -0700348 entityPrivileges = {
349 {boost::beast::http::verb::get, {{"Login"}}},
350 {boost::beast::http::verb::head, {{"Login"}}},
351 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
352 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
353 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
354 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
355 }
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100356
Ed Tanous1abe55e2018-09-05 08:30:59 -0700357 private:
358 void doGet(crow::Response& res, const crow::Request& req,
359 const std::vector<std::string>& params) override
360 {
361 res.jsonValue = Node::json;
362 res.end();
363 }
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100364};
365
Ed Tanous1abe55e2018-09-05 08:30:59 -0700366} // namespace redfish