blob: ca1c3757dac5b2e44abc0b679bf4b458a2a7e432 [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
Ed Tanousb9845d92018-07-24 14:38:06 -0700170 res.addHeader("Location",
171 "/redfish/v1/SessionService/Sessions/" + session->uniqueId);
172
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100173 // Return data for created session
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700174 memberSession.doGet(res, req, {session->uniqueId});
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100175
176 // No need for res.end(), as it is called by doGet()
177 }
178
179 /**
180 * @brief Verifies data provided in request and tries to authenticate user
181 *
182 * @param[in] req Crow request containing authentication data
183 * @param[out] httpRespCode HTTP Code that should be returned in response
184 * @param[out] user Retrieved username - not filled on failure
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100185 * @param[out] errJson JSON to which error messages will be written
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100186 *
187 * @return true if authentication was successful, false otherwise
188 */
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700189 bool authenticateUser(const crow::Request& req,
Ed Tanouse0d918b2018-03-27 17:41:04 -0700190 boost::beast::http::status& httpRespCode,
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100191 std::string& user, nlohmann::json& errJson) {
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100192 // We need only UserName and Password - nothing more, nothing less
193 static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
194
195 // call with exceptions disabled
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700196 auto loginCredentials = nlohmann::json::parse(req.body, nullptr, false);
197 if (loginCredentials.is_discarded()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700198 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100199
200 messages::addMessageToErrorJson(errJson, messages::malformedJSON());
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100201
202 return false;
203 }
204
205 // Check that there are only as many fields as there should be
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700206 if (loginCredentials.size() != numberOfRequiredFieldsInReq) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700207 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100208
209 messages::addMessageToErrorJson(errJson, messages::malformedJSON());
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100210
211 return false;
212 }
213
214 // Find fields that we need - UserName and Password
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700215 auto userIt = loginCredentials.find("UserName");
216 auto passIt = loginCredentials.find("Password");
217 if (userIt == loginCredentials.end() || passIt == loginCredentials.end()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700218 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100219
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700220 if (userIt == loginCredentials.end()) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100221 messages::addMessageToErrorJson(errJson,
222 messages::propertyMissing("UserName"));
223 }
224
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700225 if (passIt == loginCredentials.end()) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100226 messages::addMessageToErrorJson(errJson,
227 messages::propertyMissing("Password"));
228 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100229
230 return false;
231 }
232
233 // Check that given data is of valid type (string)
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700234 if (!userIt->is_string() || !passIt->is_string()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700235 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100236
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700237 if (!userIt->is_string()) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100238 messages::addMessageToErrorJson(
239 errJson,
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700240 messages::propertyValueTypeError(userIt->dump(), "UserName"));
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100241 }
242
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700243 if (!passIt->is_string()) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100244 messages::addMessageToErrorJson(
245 errJson,
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700246 messages::propertyValueTypeError(userIt->dump(), "Password"));
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100247 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100248
249 return false;
250 }
251
252 // Extract username and password
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700253 std::string username = userIt->get<const std::string>();
254 std::string password = passIt->get<const std::string>();
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100255
256 // Verify that required fields are not empty
257 if (username.empty() || password.empty()) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700258 httpRespCode = boost::beast::http::status::bad_request;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100259
260 if (username.empty()) {
261 messages::addMessageToErrorJson(errJson,
262 messages::propertyMissing("UserName"));
263 }
264
265 if (password.empty()) {
266 messages::addMessageToErrorJson(errJson,
267 messages::propertyMissing("Password"));
268 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100269
270 return false;
271 }
272
273 // Finally - try to authenticate user
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700274 if (!pamAuthenticateUser(username, password)) {
Ed Tanouse0d918b2018-03-27 17:41:04 -0700275 httpRespCode = boost::beast::http::status::unauthorized;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100276
277 messages::addMessageToErrorJson(
278 errJson, messages::resourceAtUriUnauthorized(
Ed Tanouse0d918b2018-03-27 17:41:04 -0700279 std::string(req.url), "Invalid username or password"));
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100280
281 return false;
282 }
283
284 // User authenticated successfully
Ed Tanouse0d918b2018-03-27 17:41:04 -0700285 httpRespCode = boost::beast::http::status::ok;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100286 user = username;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100287
288 return true;
289 }
290
291 /**
292 * Member session to ensure consistency between collection's doPost and
293 * member's doGet, as they should return 100% matching data
294 */
295 Sessions memberSession;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100296};
297
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100298class SessionService : public Node {
299 public:
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800300 SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100301 Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
302 Node::json["@odata.id"] = "/redfish/v1/SessionService/";
303 Node::json["@odata.context"] =
304 "/redfish/v1/$metadata#SessionService.SessionService";
305 Node::json["Name"] = "Session Service";
Ed Tanous6c233012018-03-15 14:43:56 -0700306 Node::json["Id"] = "SessionService";
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100307 Node::json["Description"] = "Session Service";
308 Node::json["SessionTimeout"] =
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700309 crow::persistent_data::SessionStore::getInstance()
310 .getTimeoutInSeconds();
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100311 Node::json["ServiceEnabled"] = true;
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800312
Ed Tanouse0d918b2018-03-27 17:41:04 -0700313 entityPrivileges = {
314 {boost::beast::http::verb::get, {{"Login"}}},
315 {boost::beast::http::verb::head, {{"Login"}}},
316 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
317 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
318 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
319 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100320 }
321
322 private:
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700323 void doGet(crow::Response& res, const crow::Request& req,
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100324 const std::vector<std::string>& params) override {
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700325 res.jsonValue = Node::json;
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100326 res.end();
327 }
328};
329
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100330} // namespace redfish