blob: 507116da804ea7d0b15f2293039b795dc8115aba [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
35 entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
36 {crow::HTTPMethod::HEAD, {{"Login"}}},
37 {crow::HTTPMethod::PATCH, {{"ConfigureManager"}}},
38 {crow::HTTPMethod::PUT, {{"ConfigureManager"}}},
39 {crow::HTTPMethod::DELETE, {{"ConfigureManager"}}},
40 {crow::HTTPMethod::POST, {{"ConfigureManager"}}}};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010041 }
42
43 private:
44 void doGet(crow::response& res, const crow::request& req,
45 const std::vector<std::string>& params) override {
46 auto session =
47 crow::PersistentData::session_store->get_session_by_uid(params[0]);
48
49 if (session == nullptr) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010050 messages::addMessageToErrorJson(
51 res.json_value, messages::resourceNotFound("Session", params[0]));
52
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010053 res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
54 res.end();
55 return;
56 }
57
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +010058 Node::json["Id"] = session->unique_id;
59 Node::json["UserName"] = session->username;
60 Node::json["@odata.id"] =
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010061 "/redfish/v1/SessionService/Sessions/" + session->unique_id;
62
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +010063 res.json_value = Node::json;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010064 res.end();
65 }
66
67 void doDelete(crow::response& res, const crow::request& req,
68 const std::vector<std::string>& params) override {
69 // Need only 1 param which should be id of session to be deleted
70 if (params.size() != 1) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010071 // This should be handled by crow and never happen
72 CROW_LOG_ERROR
73 << "Session DELETE has been called with invalid number of params";
74
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010075 res.code = static_cast<int>(HttpRespCode::BAD_REQUEST);
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010076 messages::addMessageToErrorJson(res.json_value, messages::generalError());
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010077 res.end();
78 return;
79 }
80
81 auto session =
82 crow::PersistentData::session_store->get_session_by_uid(params[0]);
83
84 if (session == nullptr) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010085 messages::addMessageToErrorJson(
86 res.json_value, messages::resourceNotFound("Session", params[0]));
87
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010088 res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
89 res.end();
90 return;
91 }
92
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +010093 // DELETE should return representation of object that will be removed
94 doGet(res, req, params);
95
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010096 crow::PersistentData::session_store->remove_session(session);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +010097 }
98
99 /**
100 * This allows SessionCollection to reuse this class' doGet method, to
101 * maintain consistency of returned data, as Collection's doPost should return
102 * data for created member which should match member's doGet result in 100%
103 */
104 friend SessionCollection;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100105};
106
107class SessionCollection : public Node {
108 public:
Borawski.Lukasz43a095a2018-02-19 15:39:01 +0100109 SessionCollection(CrowApp& app)
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800110 : Node(app, "/redfish/v1/SessionService/Sessions/"), memberSession(app) {
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100111 Node::json["@odata.type"] = "#SessionCollection.SessionCollection";
112 Node::json["@odata.id"] = "/redfish/v1/SessionService/Sessions/";
113 Node::json["@odata.context"] =
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100114 "/redfish/v1/$metadata#SessionCollection.SessionCollection";
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100115 Node::json["Name"] = "Session Collection";
116 Node::json["Description"] = "Session Collection";
117 Node::json["Members@odata.count"] = 0;
118 Node::json["Members"] = nlohmann::json::array();
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800119
120 entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
121 {crow::HTTPMethod::HEAD, {{"Login"}}},
122 {crow::HTTPMethod::PATCH, {{"ConfigureManager"}}},
123 {crow::HTTPMethod::PUT, {{"ConfigureManager"}}},
124 {crow::HTTPMethod::DELETE, {{"ConfigureManager"}}},
125 {crow::HTTPMethod::POST, {}}};
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100126 }
127
128 private:
129 void doGet(crow::response& res, const crow::request& req,
130 const std::vector<std::string>& params) override {
131 std::vector<const std::string*> session_ids =
132 crow::PersistentData::session_store->get_unique_ids(
133 false, crow::PersistentData::PersistenceType::TIMEOUT);
134
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100135 Node::json["Members@odata.count"] = session_ids.size();
136 Node::json["Members"] = nlohmann::json::array();
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100137 for (const auto& uid : session_ids) {
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100138 Node::json["Members"].push_back(
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100139 {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
140 }
141
Borawski.Lukaszc1a46bd2018-02-08 13:31:59 +0100142 res.json_value = Node::json;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100143 res.end();
144 }
145
146 void doPost(crow::response& res, const crow::request& req,
147 const std::vector<std::string>& params) override {
148 std::string username;
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100149 bool userAuthSuccessful =
150 authenticateUser(req, res.code, username, res.json_value);
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100151 if (!userAuthSuccessful) {
152 res.end();
153 return;
154 }
155
156 // User is authenticated - create session for him
157 auto session =
158 crow::PersistentData::session_store->generate_user_session(username);
159 res.add_header("X-Auth-Token", session.session_token);
160
161 // Return data for created session
162 memberSession.doGet(res, req, {session.unique_id});
163
164 // No need for res.end(), as it is called by doGet()
165 }
166
167 /**
168 * @brief Verifies data provided in request and tries to authenticate user
169 *
170 * @param[in] req Crow request containing authentication data
171 * @param[out] httpRespCode HTTP Code that should be returned in response
172 * @param[out] user Retrieved username - not filled on failure
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100173 * @param[out] errJson JSON to which error messages will be written
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100174 *
175 * @return true if authentication was successful, false otherwise
176 */
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100177 bool authenticateUser(const crow::request& req, int& httpRespCode,
178 std::string& user, nlohmann::json& errJson) {
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100179 // We need only UserName and Password - nothing more, nothing less
180 static constexpr const unsigned int numberOfRequiredFieldsInReq = 2;
181
182 // call with exceptions disabled
183 auto login_credentials = nlohmann::json::parse(req.body, nullptr, false);
184 if (login_credentials.is_discarded()) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100185 httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
186
187 messages::addMessageToErrorJson(errJson, messages::malformedJSON());
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100188
189 return false;
190 }
191
192 // Check that there are only as many fields as there should be
193 if (login_credentials.size() != numberOfRequiredFieldsInReq) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100194 httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
195
196 messages::addMessageToErrorJson(errJson, messages::malformedJSON());
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100197
198 return false;
199 }
200
201 // Find fields that we need - UserName and Password
202 auto user_it = login_credentials.find("UserName");
203 auto pass_it = login_credentials.find("Password");
204 if (user_it == login_credentials.end() ||
205 pass_it == login_credentials.end()) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100206 httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
207
208 if (user_it == login_credentials.end()) {
209 messages::addMessageToErrorJson(errJson,
210 messages::propertyMissing("UserName"));
211 }
212
213 if (pass_it == login_credentials.end()) {
214 messages::addMessageToErrorJson(errJson,
215 messages::propertyMissing("Password"));
216 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100217
218 return false;
219 }
220
221 // Check that given data is of valid type (string)
222 if (!user_it->is_string() || !pass_it->is_string()) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100223 httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
224
225 if (!user_it->is_string()) {
226 messages::addMessageToErrorJson(
227 errJson,
228 messages::propertyValueTypeError(user_it->dump(), "UserName"));
229 }
230
231 if (!pass_it->is_string()) {
232 messages::addMessageToErrorJson(
233 errJson,
234 messages::propertyValueTypeError(user_it->dump(), "Password"));
235 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100236
237 return false;
238 }
239
240 // Extract username and password
241 std::string username = user_it->get<const std::string>();
242 std::string password = pass_it->get<const std::string>();
243
244 // Verify that required fields are not empty
245 if (username.empty() || password.empty()) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100246 httpRespCode = static_cast<int>(HttpRespCode::BAD_REQUEST);
247
248 if (username.empty()) {
249 messages::addMessageToErrorJson(errJson,
250 messages::propertyMissing("UserName"));
251 }
252
253 if (password.empty()) {
254 messages::addMessageToErrorJson(errJson,
255 messages::propertyMissing("Password"));
256 }
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100257
258 return false;
259 }
260
261 // Finally - try to authenticate user
262 if (!pam_authenticate_user(username, password)) {
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100263 httpRespCode = static_cast<int>(HttpRespCode::UNAUTHORIZED);
264
265 messages::addMessageToErrorJson(
266 errJson, messages::resourceAtUriUnauthorized(
267 req.url, "Invalid username or password"));
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100268
269 return false;
270 }
271
272 // User authenticated successfully
Kowalski, Kamilf4c4dcf2018-01-29 14:55:35 +0100273 httpRespCode = static_cast<int>(HttpRespCode::OK);
274 user = username;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100275
276 return true;
277 }
278
279 /**
280 * Member session to ensure consistency between collection's doPost and
281 * member's doGet, as they should return 100% matching data
282 */
283 Sessions memberSession;
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100284};
285
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100286class SessionService : public Node {
287 public:
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800288 SessionService(CrowApp& app) : Node(app, "/redfish/v1/SessionService/") {
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100289 Node::json["@odata.type"] = "#SessionService.v1_0_2.SessionService";
290 Node::json["@odata.id"] = "/redfish/v1/SessionService/";
291 Node::json["@odata.context"] =
292 "/redfish/v1/$metadata#SessionService.SessionService";
293 Node::json["Name"] = "Session Service";
Ed Tanous6c233012018-03-15 14:43:56 -0700294 Node::json["Id"] = "SessionService";
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100295 Node::json["Description"] = "Session Service";
296 Node::json["SessionTimeout"] =
297 crow::PersistentData::session_store->get_timeout_in_seconds();
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100298 Node::json["ServiceEnabled"] = true;
Ed Tanous3ebd75f2018-03-05 18:20:01 -0800299
300 entityPrivileges = {{crow::HTTPMethod::GET, {{"Login"}}},
301 {crow::HTTPMethod::HEAD, {{"Login"}}},
302 {crow::HTTPMethod::PATCH, {{"ConfigureManager"}}},
303 {crow::HTTPMethod::PUT, {{"ConfigureManager"}}},
304 {crow::HTTPMethod::DELETE, {{"ConfigureManager"}}},
305 {crow::HTTPMethod::POST, {{"ConfigureManager"}}}};
Borawski.Lukasz5d27b852018-02-08 13:24:24 +0100306 }
307
308 private:
309 void doGet(crow::response& res, const crow::request& req,
310 const std::vector<std::string>& params) override {
311 res.json_value = Node::json;
312 res.end();
313 }
314};
315
Kowalski, Kamil2b7981f2018-01-31 13:24:59 +0100316} // namespace redfish