blob: 9e793df049361406117c26c2c86c21495a7b78f0 [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
22namespace redfish {
23
24class SessionCollection;
25
26class Sessions : public Node {
27 public:
Borawski.Lukasz43a095a2018-02-19 15:39:01 +010028 Sessions(CrowApp& app)
Ed Tanous6c233012018-03-15 14:43:56 -070029 : Node(app, "/redfish/v1/SessionService/Sessions/<str>/", std::string()) {
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +010030 Node::json["@odata.type"] = "#Session.v1_0_2.Session";
31 Node::json["@odata.context"] = "/redfish/v1/$metadata#Session.Session";
32 Node::json["Name"] = "User Session";
33 Node::json["Description"] = "Manager User Session";
Ed Tanous3ebd75f2018-03-05 18:20:01 -080034
Ed Tanouse0d918b2018-03-27 17:41:04 -070035 entityPrivileges = {
36 {boost::beast::http::verb::get, {{"Login"}}},
37 {boost::beast::http::verb::head, {{"Login"}}},
38 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
39 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
40 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
41 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010042 }
43
44 private:
45 void doGet(crow::response& res, const crow::request& req,
46 const std::vector<std::string>& params) override {
47 auto session =
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020048 crow::PersistentData::SessionStore::getInstance().get_session_by_uid(
49 params[0]);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010050
51 if (session == nullptr) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010052 messages::addMessageToErrorJson(
53 res.json_value, messages::resourceNotFound("Session", params[0]));
54
Ed Tanouse0d918b2018-03-27 17:41:04 -070055 res.result(boost::beast::http::status::not_found);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010056 res.end();
57 return;
58 }
59
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +010060 Node::json["Id"] = session->unique_id;
61 Node::json["UserName"] = session->username;
62 Node::json["@odata.id"] =
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010063 "/redfish/v1/SessionService/Sessions/" + session->unique_id;
64
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +010065 res.json_value = Node::json;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010066 res.end();
67 }
68
69 void doDelete(crow::response& res, const crow::request& req,
70 const std::vector<std::string>& params) override {
71 // Need only 1 param which should be id of session to be deleted
72 if (params.size() != 1) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010073 // This should be handled by crow and never happen
74 CROW_LOG_ERROR
75 << "Session DELETE has been called with invalid number of params";
76
Ed Tanouse0d918b2018-03-27 17:41:04 -070077 res.result(boost::beast::http::status::bad_request);
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010078 messages::addMessageToErrorJson(res.json_value, messages::generalError());
Ed Tanouse0d918b2018-03-27 17:41:04 -070079
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010080 res.end();
81 return;
82 }
83
84 auto session =
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020085 crow::PersistentData::SessionStore::getInstance().get_session_by_uid(
86 params[0]);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010087
88 if (session == nullptr) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010089 messages::addMessageToErrorJson(
90 res.json_value, messages::resourceNotFound("Session", params[0]));
91
Ed Tanouse0d918b2018-03-27 17:41:04 -070092 res.result(boost::beast::http::status::not_found);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010093 res.end();
94 return;
95 }
96
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010097 // DELETE should return representation of object that will be removed
98 doGet(res, req, params);
99
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200100 crow::PersistentData::SessionStore::getInstance().remove_session(session);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100101 }
102
103 /**
104 * This allows SessionCollection to reuse this class' doGet method, to
105 * maintain consistency of returned data, as Collection's doPost should return
106 * data for created member which should match member's doGet result in 100%
107 */
108 friend SessionCollection;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100109};
110
111class SessionCollection : public Node {
112 public:
Borawski.Lukasz43a095a2018-02-19 15:39:01 +0100113 SessionCollection(CrowApp& app)
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800114 : Node(app, "/redfish/v1/SessionService/Sessions/"), memberSession(app) {
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100115 Node::json["@odata.type"] = "#SessionCollection.SessionCollection";
116 Node::json["@odata.id"] = "/redfish/v1/SessionService/Sessions/";
117 Node::json["@odata.context"] =
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100118 "/redfish/v1/$metadata#SessionCollection.SessionCollection";
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100119 Node::json["Name"] = "Session Collection";
120 Node::json["Description"] = "Session Collection";
121 Node::json["Members@odata.count"] = 0;
122 Node::json["Members"] = nlohmann::json::array();
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800123
Ed Tanouse0d918b2018-03-27 17:41:04 -0700124 entityPrivileges = {
125 {boost::beast::http::verb::get, {{"Login"}}},
126 {boost::beast::http::verb::head, {{"Login"}}},
127 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
128 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
129 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
130 {boost::beast::http::verb::post, {}}};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100131 }
132
133 private:
134 void doGet(crow::response& res, const crow::request& req,
135 const std::vector<std::string>& params) override {
136 std::vector<const std::string*> session_ids =
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200137 crow::PersistentData::SessionStore::getInstance().get_unique_ids(
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100138 false, crow::PersistentData::PersistenceType::TIMEOUT);
139
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100140 Node::json["Members@odata.count"] = session_ids.size();
141 Node::json["Members"] = nlohmann::json::array();
Ed Tanouse0d918b2018-03-27 17:41:04 -0700142 for (const std::string* uid : session_ids) {
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100143 Node::json["Members"].push_back(
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100144 {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
145 }
146
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100147 res.json_value = Node::json;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100148 res.end();
149 }
150
151 void doPost(crow::response& res, const crow::request& req,
152 const std::vector<std::string>& params) override {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700153 boost::beast::http::status status;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100154 std::string username;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100155 bool userAuthSuccessful =
Ed Tanouse0d918b2018-03-27 17:41:04 -0700156 authenticateUser(req, status, username, res.json_value);
157 res.result(status);
158
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100159 if (!userAuthSuccessful) {
160 res.end();
161 return;
162 }
163
164 // User is authenticated - create session for him
165 auto session =
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200166 crow::PersistentData::SessionStore::getInstance().generate_user_session(
167 username);
Ed Tanouse0d918b2018-03-27 17:41:04 -0700168 res.add_header("X-Auth-Token", session->session_token);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100169
170 // Return data for created session
Ed Tanouse0d918b2018-03-27 17:41:04 -0700171 memberSession.doGet(res, req, {session->unique_id});
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100172
173 // No need for res.end(), as it is called by doGet()
174 }
175
176 /**
177 * @brief Verifies data provided in request and tries to authenticate user
178 *
179 * @param[in] req Crow request containing authentication data
180 * @param[out] httpRespCode HTTP Code that should be returned in response
181 * @param[out] user Retrieved username - not filled on failure
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100182 * @param[out] errJson JSON to which error messages will be written
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100183 *
184 * @return true if authentication was successful, false otherwise
185 */
Ed Tanouse0d918b2018-03-27 17:41:04 -0700186 bool authenticateUser(const crow::request& req,
187 boost::beast::http::status& httpRespCode,
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100188 std::string& user, nlohmann::json& errJson) {
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100189 // We need only UserName and Password - nothing more, nothing less
190 static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
191
192 // call with exceptions disabled
193 auto login_credentials = nlohmann::json::parse(req.body, nullptr, false);
194 if (login_credentials.is_discarded()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700195 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100196
197 messages::addMessageToErrorJson(errJson, messages::malformedJSON());
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100198
199 return false;
200 }
201
202 // Check that there are only as many fields as there should be
203 if (login_credentials.size() != numberOfRequiredFieldsInReq) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700204 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100205
206 messages::addMessageToErrorJson(errJson, messages::malformedJSON());
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100207
208 return false;
209 }
210
211 // Find fields that we need - UserName and Password
212 auto user_it = login_credentials.find("UserName");
213 auto pass_it = login_credentials.find("Password");
214 if (user_it == login_credentials.end() ||
215 pass_it == login_credentials.end()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700216 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100217
218 if (user_it == login_credentials.end()) {
219 messages::addMessageToErrorJson(errJson,
220 messages::propertyMissing("UserName"));
221 }
222
223 if (pass_it == login_credentials.end()) {
224 messages::addMessageToErrorJson(errJson,
225 messages::propertyMissing("Password"));
226 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100227
228 return false;
229 }
230
231 // Check that given data is of valid type (string)
232 if (!user_it->is_string() || !pass_it->is_string()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700233 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100234
235 if (!user_it->is_string()) {
236 messages::addMessageToErrorJson(
237 errJson,
238 messages::propertyValueTypeError(user_it->dump(), "UserName"));
239 }
240
241 if (!pass_it->is_string()) {
242 messages::addMessageToErrorJson(
243 errJson,
244 messages::propertyValueTypeError(user_it->dump(), "Password"));
245 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100246
247 return false;
248 }
249
250 // Extract username and password
251 std::string username = user_it->get<const std::string>();
252 std::string password = pass_it->get<const std::string>();
253
254 // Verify that required fields are not empty
255 if (username.empty() || password.empty()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700256 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100257
258 if (username.empty()) {
259 messages::addMessageToErrorJson(errJson,
260 messages::propertyMissing("UserName"));
261 }
262
263 if (password.empty()) {
264 messages::addMessageToErrorJson(errJson,
265 messages::propertyMissing("Password"));
266 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100267
268 return false;
269 }
270
271 // Finally - try to authenticate user
272 if (!pam_authenticate_user(username, password)) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700273 httpRespCode = boost::beast::http::status::unauthorized;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100274
275 messages::addMessageToErrorJson(
276 errJson, messages::resourceAtUriUnauthorized(
Ed Tanouse0d918b2018-03-27 17:41:04 -0700277 std::string(req.url), "Invalid username or password"));
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100278
279 return false;
280 }
281
282 // User authenticated successfully
Ed Tanouse0d918b2018-03-27 17:41:04 -0700283 httpRespCode = boost::beast::http::status::ok;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100284 user = username;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100285
286 return true;
287 }
288
289 /**
290 * Member session to ensure consistency between collection's doPost and
291 * member's doGet, as they should return 100% matching data
292 */
293 Sessions memberSession;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100294};
295
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100296class SessionService : public Node {
297 public:
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800298 SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100299 Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
300 Node::json["@odata.id"] = "/redfish/v1/SessionService/";
301 Node::json["@odata.context"] =
302 "/redfish/v1/$metadata#SessionService.SessionService";
303 Node::json["Name"] = "Session Service";
Ed Tanous6c233012018-03-15 14:43:56 -0700304 Node::json["Id"] = "SessionService";
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100305 Node::json["Description"] = "Session Service";
306 Node::json["SessionTimeout"] =
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200307 crow::PersistentData::SessionStore::getInstance()
308 .get_timeout_in_seconds();
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100309 Node::json["ServiceEnabled"] = true;
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800310
Ed Tanouse0d918b2018-03-27 17:41:04 -0700311 entityPrivileges = {
312 {boost::beast::http::verb::get, {{"Login"}}},
313 {boost::beast::http::verb::head, {{"Login"}}},
314 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
315 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
316 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
317 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100318 }
319
320 private:
321 void doGet(crow::response& res, const crow::request& req,
322 const std::vector<std::string>& params) override {
323 res.json_value = Node::json;
324 res.end();
325 }
326};
327
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100328} // namespace redfish