blob: a32a6607545de8b26f3f3266f255e0ccfd6cdb76 [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:
Ed Tanous55c7b7a2018-05-22 15:27:24 -070045 void doGet(crow::Response& res, const crow::Request& req,
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010046 const std::vector<std::string>& params) override {
47 auto session =
Ed Tanous55c7b7a2018-05-22 15:27:24 -070048 crow::persistent_data::SessionStore::getInstance().getSessionByUid(
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020049 params[0]);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010050
51 if (session == nullptr) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010052 messages::addMessageToErrorJson(
Ed Tanous55c7b7a2018-05-22 15:27:24 -070053 res.jsonValue, messages::resourceNotFound("Session", params[0]));
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010054
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
Ed Tanous55c7b7a2018-05-22 15:27:24 -070060 Node::json["Id"] = session->uniqueId;
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +010061 Node::json["UserName"] = session->username;
62 Node::json["@odata.id"] =
Ed Tanous55c7b7a2018-05-22 15:27:24 -070063 "/redfish/v1/SessionService/Sessions/" + session->uniqueId;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010064
Ed Tanous55c7b7a2018-05-22 15:27:24 -070065 res.jsonValue = Node::json;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010066 res.end();
67 }
68
Ed Tanous55c7b7a2018-05-22 15:27:24 -070069 void doDelete(crow::Response& res, const crow::Request& req,
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010070 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
Ed Tanous55c7b7a2018-05-22 15:27:24 -070074 BMCWEB_LOG_ERROR
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010075 << "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);
Ed Tanous55c7b7a2018-05-22 15:27:24 -070078 messages::addMessageToErrorJson(res.jsonValue, 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 =
Ed Tanous55c7b7a2018-05-22 15:27:24 -070085 crow::persistent_data::SessionStore::getInstance().getSessionByUid(
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020086 params[0]);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010087
88 if (session == nullptr) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010089 messages::addMessageToErrorJson(
Ed Tanous55c7b7a2018-05-22 15:27:24 -070090 res.jsonValue, messages::resourceNotFound("Session", params[0]));
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010091
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
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700100 crow::persistent_data::SessionStore::getInstance().removeSession(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:
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700134 void doGet(crow::Response& res, const crow::Request& req,
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100135 const std::vector<std::string>& params) override {
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700136 std::vector<const std::string*> sessionIds =
137 crow::persistent_data::SessionStore::getInstance().getUniqueIds(
138 false, crow::persistent_data::PersistenceType::TIMEOUT);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100139
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700140 Node::json["Members@odata.count"] = sessionIds.size();
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100141 Node::json["Members"] = nlohmann::json::array();
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700142 for (const std::string* uid : sessionIds) {
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
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700147 res.jsonValue = Node::json;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100148 res.end();
149 }
150
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700151 void doPost(crow::Response& res, const crow::Request& req,
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100152 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 Tanous55c7b7a2018-05-22 15:27:24 -0700156 authenticateUser(req, status, username, res.jsonValue);
Ed Tanouse0d918b2018-03-27 17:41:04 -0700157 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 =
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700166 crow::persistent_data::SessionStore::getInstance().generateUserSession(
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200167 username);
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700168 res.addHeader("X-Auth-Token", session->sessionToken);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100169
170 // Return data for created session
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700171 memberSession.doGet(res, req, {session->uniqueId});
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 Tanous55c7b7a2018-05-22 15:27:24 -0700186 bool authenticateUser(const crow::Request& req,
Ed Tanouse0d918b2018-03-27 17:41:04 -0700187 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
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700193 auto loginCredentials = nlohmann::json::parse(req.body, nullptr, false);
194 if (loginCredentials.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
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700203 if (loginCredentials.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
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700212 auto userIt = loginCredentials.find("UserName");
213 auto passIt = loginCredentials.find("Password");
214 if (userIt == loginCredentials.end() || passIt == loginCredentials.end()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700215 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100216
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700217 if (userIt == loginCredentials.end()) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100218 messages::addMessageToErrorJson(errJson,
219 messages::propertyMissing("UserName"));
220 }
221
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700222 if (passIt == loginCredentials.end()) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100223 messages::addMessageToErrorJson(errJson,
224 messages::propertyMissing("Password"));
225 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100226
227 return false;
228 }
229
230 // Check that given data is of valid type (string)
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700231 if (!userIt->is_string() || !passIt->is_string()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700232 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100233
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700234 if (!userIt->is_string()) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100235 messages::addMessageToErrorJson(
236 errJson,
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700237 messages::propertyValueTypeError(userIt->dump(), "UserName"));
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100238 }
239
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700240 if (!passIt->is_string()) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100241 messages::addMessageToErrorJson(
242 errJson,
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700243 messages::propertyValueTypeError(userIt->dump(), "Password"));
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100244 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100245
246 return false;
247 }
248
249 // Extract username and password
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700250 std::string username = userIt->get<const std::string>();
251 std::string password = passIt->get<const std::string>();
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100252
253 // Verify that required fields are not empty
254 if (username.empty() || password.empty()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700255 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100256
257 if (username.empty()) {
258 messages::addMessageToErrorJson(errJson,
259 messages::propertyMissing("UserName"));
260 }
261
262 if (password.empty()) {
263 messages::addMessageToErrorJson(errJson,
264 messages::propertyMissing("Password"));
265 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100266
267 return false;
268 }
269
270 // Finally - try to authenticate user
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700271 if (!pamAuthenticateUser(username, password)) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700272 httpRespCode = boost::beast::http::status::unauthorized;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100273
274 messages::addMessageToErrorJson(
275 errJson, messages::resourceAtUriUnauthorized(
Ed Tanouse0d918b2018-03-27 17:41:04 -0700276 std::string(req.url), "Invalid username or password"));
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100277
278 return false;
279 }
280
281 // User authenticated successfully
Ed Tanouse0d918b2018-03-27 17:41:04 -0700282 httpRespCode = boost::beast::http::status::ok;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100283 user = username;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100284
285 return true;
286 }
287
288 /**
289 * Member session to ensure consistency between collection's doPost and
290 * member's doGet, as they should return 100% matching data
291 */
292 Sessions memberSession;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100293};
294
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100295class SessionService : public Node {
296 public:
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800297 SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100298 Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
299 Node::json["@odata.id"] = "/redfish/v1/SessionService/";
300 Node::json["@odata.context"] =
301 "/redfish/v1/$metadata#SessionService.SessionService";
302 Node::json["Name"] = "Session Service";
Ed Tanous6c233012018-03-15 14:43:56 -0700303 Node::json["Id"] = "SessionService";
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100304 Node::json["Description"] = "Session Service";
305 Node::json["SessionTimeout"] =
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700306 crow::persistent_data::SessionStore::getInstance()
307 .getTimeoutInSeconds();
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100308 Node::json["ServiceEnabled"] = true;
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800309
Ed Tanouse0d918b2018-03-27 17:41:04 -0700310 entityPrivileges = {
311 {boost::beast::http::verb::get, {{"Login"}}},
312 {boost::beast::http::verb::head, {{"Login"}}},
313 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
314 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
315 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
316 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100317 }
318
319 private:
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700320 void doGet(crow::Response& res, const crow::Request& req,
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100321 const std::vector<std::string>& params) override {
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700322 res.jsonValue = Node::json;
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100323 res.end();
324 }
325};
326
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100327} // namespace redfish