blob: 7de6d3ba9a2b147bc2443199d05d3ff665b5da83 [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"
20#include "session_storage_singleton.hpp"
21
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 =
48 crow::PersistentData::session_store->get_session_by_uid(params[0]);
49
50 if (session == nullptr) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010051 messages::addMessageToErrorJson(
52 res.json_value, messages::resourceNotFound("Session", params[0]));
53
Ed Tanouse0d918b2018-03-27 17:41:04 -070054 res.result(boost::beast::http::status::not_found);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010055 res.end();
56 return;
57 }
58
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +010059 Node::json["Id"] = session->unique_id;
60 Node::json["UserName"] = session->username;
61 Node::json["@odata.id"] =
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010062 "/redfish/v1/SessionService/Sessions/" + session->unique_id;
63
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +010064 res.json_value = Node::json;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010065 res.end();
66 }
67
68 void doDelete(crow::response& res, const crow::request& req,
69 const std::vector<std::string>& params) override {
70 // Need only 1 param which should be id of session to be deleted
71 if (params.size() != 1) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010072 // This should be handled by crow and never happen
73 CROW_LOG_ERROR
74 << "Session DELETE has been called with invalid number of params";
75
Ed Tanouse0d918b2018-03-27 17:41:04 -070076 res.result(boost::beast::http::status::bad_request);
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010077 messages::addMessageToErrorJson(res.json_value, messages::generalError());
Ed Tanouse0d918b2018-03-27 17:41:04 -070078
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010079 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) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010087 messages::addMessageToErrorJson(
88 res.json_value, messages::resourceNotFound("Session", params[0]));
89
Ed Tanouse0d918b2018-03-27 17:41:04 -070090 res.result(boost::beast::http::status::not_found);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010091 res.end();
92 return;
93 }
94
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010095 // DELETE should return representation of object that will be removed
96 doGet(res, req, params);
97
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010098 crow::PersistentData::session_store->remove_session(session);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010099 }
100
101 /**
102 * This allows SessionCollection to reuse this class' doGet method, to
103 * maintain consistency of returned data, as Collection's doPost should return
104 * data for created member which should match member's doGet result in 100%
105 */
106 friend SessionCollection;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100107};
108
109class SessionCollection : public Node {
110 public:
Borawski.Lukasz43a095a2018-02-19 15:39:01 +0100111 SessionCollection(CrowApp& app)
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800112 : Node(app, "/redfish/v1/SessionService/Sessions/"), memberSession(app) {
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100113 Node::json["@odata.type"] = "#SessionCollection.SessionCollection";
114 Node::json["@odata.id"] = "/redfish/v1/SessionService/Sessions/";
115 Node::json["@odata.context"] =
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100116 "/redfish/v1/$metadata#SessionCollection.SessionCollection";
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100117 Node::json["Name"] = "Session Collection";
118 Node::json["Description"] = "Session Collection";
119 Node::json["Members@odata.count"] = 0;
120 Node::json["Members"] = nlohmann::json::array();
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800121
Ed Tanouse0d918b2018-03-27 17:41:04 -0700122 entityPrivileges = {
123 {boost::beast::http::verb::get, {{"Login"}}},
124 {boost::beast::http::verb::head, {{"Login"}}},
125 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
126 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
127 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
128 {boost::beast::http::verb::post, {}}};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100129 }
130
131 private:
132 void doGet(crow::response& res, const crow::request& req,
133 const std::vector<std::string>& params) override {
134 std::vector<const std::string*> session_ids =
135 crow::PersistentData::session_store->get_unique_ids(
136 false, crow::PersistentData::PersistenceType::TIMEOUT);
137
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100138 Node::json["Members@odata.count"] = session_ids.size();
139 Node::json["Members"] = nlohmann::json::array();
Ed Tanouse0d918b2018-03-27 17:41:04 -0700140 for (const std::string* uid : session_ids) {
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100141 Node::json["Members"].push_back(
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100142 {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
143 }
144
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100145 res.json_value = Node::json;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100146 res.end();
147 }
148
149 void doPost(crow::response& res, const crow::request& req,
150 const std::vector<std::string>& params) override {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700151 boost::beast::http::status status;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100152 std::string username;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100153 bool userAuthSuccessful =
Ed Tanouse0d918b2018-03-27 17:41:04 -0700154 authenticateUser(req, status, username, res.json_value);
155 res.result(status);
156
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100157 if (!userAuthSuccessful) {
158 res.end();
159 return;
160 }
161
162 // User is authenticated - create session for him
163 auto session =
164 crow::PersistentData::session_store->generate_user_session(username);
Ed Tanouse0d918b2018-03-27 17:41:04 -0700165 res.add_header("X-Auth-Token", session->session_token);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100166
167 // Return data for created session
Ed Tanouse0d918b2018-03-27 17:41:04 -0700168 memberSession.doGet(res, req, {session->unique_id});
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100169
170 // No need for res.end(), as it is called by doGet()
171 }
172
173 /**
174 * @brief Verifies data provided in request and tries to authenticate user
175 *
176 * @param[in] req Crow request containing authentication data
177 * @param[out] httpRespCode HTTP Code that should be returned in response
178 * @param[out] user Retrieved username - not filled on failure
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100179 * @param[out] errJson JSON to which error messages will be written
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100180 *
181 * @return true if authentication was successful, false otherwise
182 */
Ed Tanouse0d918b2018-03-27 17:41:04 -0700183 bool authenticateUser(const crow::request& req,
184 boost::beast::http::status& httpRespCode,
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100185 std::string& user, nlohmann::json& errJson) {
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100186 // We need only UserName and Password - nothing more, nothing less
187 static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
188
189 // call with exceptions disabled
190 auto login_credentials = nlohmann::json::parse(req.body, nullptr, false);
191 if (login_credentials.is_discarded()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700192 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100193
194 messages::addMessageToErrorJson(errJson, messages::malformedJSON());
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100195
196 return false;
197 }
198
199 // Check that there are only as many fields as there should be
200 if (login_credentials.size() != numberOfRequiredFieldsInReq) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700201 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100202
203 messages::addMessageToErrorJson(errJson, messages::malformedJSON());
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100204
205 return false;
206 }
207
208 // Find fields that we need - UserName and Password
209 auto user_it = login_credentials.find("UserName");
210 auto pass_it = login_credentials.find("Password");
211 if (user_it == login_credentials.end() ||
212 pass_it == login_credentials.end()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700213 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100214
215 if (user_it == login_credentials.end()) {
216 messages::addMessageToErrorJson(errJson,
217 messages::propertyMissing("UserName"));
218 }
219
220 if (pass_it == login_credentials.end()) {
221 messages::addMessageToErrorJson(errJson,
222 messages::propertyMissing("Password"));
223 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100224
225 return false;
226 }
227
228 // Check that given data is of valid type (string)
229 if (!user_it->is_string() || !pass_it->is_string()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700230 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100231
232 if (!user_it->is_string()) {
233 messages::addMessageToErrorJson(
234 errJson,
235 messages::propertyValueTypeError(user_it->dump(), "UserName"));
236 }
237
238 if (!pass_it->is_string()) {
239 messages::addMessageToErrorJson(
240 errJson,
241 messages::propertyValueTypeError(user_it->dump(), "Password"));
242 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100243
244 return false;
245 }
246
247 // Extract username and password
248 std::string username = user_it->get<const std::string>();
249 std::string password = pass_it->get<const std::string>();
250
251 // Verify that required fields are not empty
252 if (username.empty() || password.empty()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700253 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100254
255 if (username.empty()) {
256 messages::addMessageToErrorJson(errJson,
257 messages::propertyMissing("UserName"));
258 }
259
260 if (password.empty()) {
261 messages::addMessageToErrorJson(errJson,
262 messages::propertyMissing("Password"));
263 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100264
265 return false;
266 }
267
268 // Finally - try to authenticate user
269 if (!pam_authenticate_user(username, password)) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700270 httpRespCode = boost::beast::http::status::unauthorized;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100271
272 messages::addMessageToErrorJson(
273 errJson, messages::resourceAtUriUnauthorized(
Ed Tanouse0d918b2018-03-27 17:41:04 -0700274 std::string(req.url), "Invalid username or password"));
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100275
276 return false;
277 }
278
279 // User authenticated successfully
Ed Tanouse0d918b2018-03-27 17:41:04 -0700280 httpRespCode = boost::beast::http::status::ok;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100281 user = username;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100282
283 return true;
284 }
285
286 /**
287 * Member session to ensure consistency between collection's doPost and
288 * member's doGet, as they should return 100% matching data
289 */
290 Sessions memberSession;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100291};
292
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100293class SessionService : public Node {
294 public:
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800295 SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100296 Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
297 Node::json["@odata.id"] = "/redfish/v1/SessionService/";
298 Node::json["@odata.context"] =
299 "/redfish/v1/$metadata#SessionService.SessionService";
300 Node::json["Name"] = "Session Service";
Ed Tanous6c233012018-03-15 14:43:56 -0700301 Node::json["Id"] = "SessionService";
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100302 Node::json["Description"] = "Session Service";
303 Node::json["SessionTimeout"] =
304 crow::PersistentData::session_store->get_timeout_in_seconds();
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100305 Node::json["ServiceEnabled"] = true;
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800306
Ed Tanouse0d918b2018-03-27 17:41:04 -0700307 entityPrivileges = {
308 {boost::beast::http::verb::get, {{"Login"}}},
309 {boost::beast::http::verb::head, {{"Login"}}},
310 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
311 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
312 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
313 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100314 }
315
316 private:
317 void doGet(crow::response& res, const crow::request& req,
318 const std::vector<std::string>& params) override {
319 res.json_value = Node::json;
320 res.end();
321 }
322};
323
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100324} // namespace redfish