Line data Source code
1 : /**
2 : Copyright (c) 2024 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "SPWebSession.h"
24 : #include "SPValid.h"
25 : #include "SPCrypto.h"
26 : #include "SPDbUser.h"
27 :
28 : namespace STAPPLER_VERSIONIZED stappler::web {
29 :
30 : #define SA_SESSION_EXISTS(var, def) ((var == NULL)?def:var)
31 :
32 : static constexpr auto SA_SESSION_TOKEN_NAME = "token";
33 : static constexpr auto SA_SESSION_UUID_KEY = "uuid";
34 : static constexpr auto SA_SESSION_USER_NAME_KEY = "userName";
35 : static constexpr auto SA_SESSION_USER_ID_KEY = "userId";
36 : static constexpr auto SA_SESSION_SALT_KEY = "salt";
37 : static constexpr auto SA_SESSION_MAX_AGE_KEY = "maxAge";
38 : static constexpr auto SA_SESSION_TOKEN_LEN = 64;
39 :
40 175 : Session::Token Session::makeSessionToken(Request &rctx, const memory::uuid & uuid, const StringView & userName) {
41 175 : auto host = rctx.host();
42 175 : auto &info = rctx.getInfo();
43 :
44 350 : return crypto::hash512([&] ( const Callback<bool(const CoderSource &)> &upd ) {
45 175 : upd(uuid.view());
46 175 : upd(userName);
47 175 : upd(host.getSessionInfo().key);
48 175 : upd(info.url.host);
49 175 : upd(info.protocol);
50 175 : upd(rctx.getRequestHeader("User-Agent"));
51 525 : }, crypto::HashFunction::GOST_3411);
52 : }
53 :
54 175 : Session::Token Session::makeCookieToken(Request &rctx, const memory::uuid & uuid, const StringView & userName, const Bytes & salt) {
55 175 : auto host = rctx.host();
56 175 : auto &info = rctx.getInfo();
57 :
58 350 : return crypto::hash512([&] ( const Callback<bool(const CoderSource &)> &upd ) {
59 175 : upd(uuid.view());
60 175 : upd(userName);
61 175 : upd(host.getSessionInfo().key);
62 175 : upd(info.url.host);
63 175 : upd(info.protocol);
64 175 : upd(salt);
65 175 : upd(rctx.getRequestHeader("User-Agent"));
66 525 : }, crypto::HashFunction::GOST_3411);
67 : }
68 :
69 2850 : Session::~Session() {
70 2850 : if (isModified() && isValid() && _request) {
71 0 : save();
72 : }
73 2850 : }
74 :
75 2950 : Session::Session(const Request &rctx, bool silent) : _request(rctx) {
76 2950 : _valid = init(silent);
77 2950 : }
78 :
79 25 : Session::Session(const Request &rctx, db::User *user, TimeInterval maxAge) : _request(rctx) {
80 25 : _valid = init(user, maxAge);
81 25 : }
82 :
83 25 : bool Session::init(db::User *user, TimeInterval maxAge) {
84 25 : _maxAge = maxAge;
85 25 : _uuid = memory::uuid::generate();
86 25 : _user = user;
87 :
88 25 : auto &data = newDict("data");
89 25 : data.setString(user ? user->getName() : memory::uuid::generate().str(), SA_SESSION_USER_NAME_KEY);
90 25 : data.setInteger(user ? user->getObjectId() : 0, SA_SESSION_USER_ID_KEY);
91 25 : data.setInteger(maxAge.toMicros(), SA_SESSION_MAX_AGE_KEY);
92 25 : data.setBytes(_uuid.bytes(), SA_SESSION_UUID_KEY);
93 :
94 25 : Bytes salt; salt.resize(32);
95 25 : stappler::valid::makeRandomBytes(salt.data(), 32);
96 :
97 25 : _sessionToken = makeSessionToken(_request, _uuid, data.getString(SA_SESSION_USER_NAME_KEY));
98 25 : _cookieToken = makeCookieToken(_request, _uuid, user->getName(), salt);
99 :
100 25 : data.setBytes(std::move(salt), SA_SESSION_SALT_KEY);
101 :
102 25 : setModified(false);
103 :
104 50 : return write();
105 25 : }
106 :
107 2950 : bool Session::init(bool silent) {
108 2950 : auto host = _request.host();
109 2950 : auto &info = _request.getInfo();
110 :
111 2950 : auto &sessionTokenString = info.queryData.getString(SA_SESSION_TOKEN_NAME);
112 :
113 : /* token is a base64 encoded hash from sha512, so, it must have 88 bytes */
114 2950 : if (sessionTokenString.size() != 86) {
115 2800 : if (!silent) {
116 25 : _request.addDebug("Session", "Session token format is invalid");
117 : }
118 2800 : return false;
119 : }
120 :
121 150 : Bytes sessionToken(stappler::base64url::decode<Interface>(sessionTokenString));
122 150 : auto sessionData = getStorageData(_request, sessionToken);
123 150 : auto &data = sessionData.getValue("data");
124 150 : if (!data && !silent) {
125 0 : _request.addDebug("Session", "Fail to extract session from storage");
126 : }
127 :
128 150 : auto &uuidData = data.getBytes(SA_SESSION_UUID_KEY);
129 150 : auto &userName = data.getString(SA_SESSION_USER_NAME_KEY);
130 150 : auto &salt = data.getBytes(SA_SESSION_SALT_KEY);
131 150 : if (uuidData.empty() || userName.empty()) {
132 0 : if (!silent) {
133 0 : _request.addError("Session", "Wrong authority data in session");
134 : }
135 0 : return false;
136 : }
137 :
138 150 : memory::uuid sessionUuid(uuidData);
139 :
140 150 : Token buf = makeSessionToken(_request, sessionUuid, userName);
141 :
142 150 : if (memcmp(buf.data(), sessionToken.data(), sizeof(Token)) != 0) {
143 0 : if (!silent) {
144 0 : _request.addError("Session", "Session token is invalid");
145 : }
146 0 : return false;
147 : }
148 :
149 150 : Bytes cookieToken(stappler::base64url::decode<Interface>(_request.getCookie(host.getSessionInfo().name, !silent)));
150 150 : if (cookieToken.empty() || cookieToken.size() != 64) {
151 0 : if (!silent) {
152 0 : _request.addError("Session", "Fail to read token from cookie", Value{
153 0 : std::make_pair("token", Value(cookieToken))
154 0 : });
155 : }
156 0 : return false;
157 : }
158 :
159 150 : buf = makeCookieToken(_request, sessionUuid, userName, salt);
160 :
161 150 : if (memcmp(buf.data(), cookieToken.data(), sizeof(Token)) != 0) {
162 0 : if (!silent) {
163 0 : _request.addError("Session", "Cookie token is invalid", Value{
164 0 : std::make_pair("token", Value(cookieToken)),
165 0 : std::make_pair("check", Value(Bytes(buf.begin(), buf.end())))
166 0 : });
167 : }
168 0 : return false;
169 : }
170 :
171 150 : memcpy(_cookieToken.data(), cookieToken.data(), SA_SESSION_TOKEN_LEN);
172 150 : memcpy(_sessionToken.data(), sessionToken.data(), SA_SESSION_TOKEN_LEN);
173 150 : _uuid = sessionUuid;
174 150 : _maxAge = TimeInterval::seconds(data.getInteger(SA_SESSION_MAX_AGE_KEY));
175 :
176 150 : uint64_t id = (uint64_t)data.getInteger(SA_SESSION_USER_ID_KEY);
177 150 : if (id) {
178 150 : _user = getStorageUser(_request, id);
179 150 : if (!_user) {
180 0 : if (!silent) {
181 0 : _request.addError("Session", "Invalid user id in session data");
182 : }
183 0 : return false;
184 : }
185 : }
186 :
187 150 : _data = std::move(sessionData);
188 150 : return _user != nullptr;
189 150 : }
190 :
191 0 : const Session::Token & Session::getCookieToken() const {
192 0 : return _cookieToken;
193 : }
194 :
195 3025 : bool Session::isValid() const {
196 3025 : return _valid;
197 : }
198 50 : const Session::Token & Session::getSessionToken() const {
199 50 : return _sessionToken;
200 : }
201 :
202 0 : const memory::uuid &Session::getSessionUuid() const {
203 0 : return _uuid;
204 : }
205 :
206 50 : bool Session::write() {
207 50 : if (!save()) {
208 0 : return false;
209 : }
210 :
211 50 : _request.setCookie(_request.host().getSessionInfo().name, stappler::base64url::encode<Interface>(_cookieToken), _maxAge);
212 50 : return true;
213 : }
214 :
215 50 : bool Session::save() {
216 50 : setModified(false);
217 50 : return setStorageData(_request, _sessionToken, _data, _maxAge);
218 : }
219 :
220 25 : bool Session::cancel() {
221 25 : clearStorageData(_request, _sessionToken);
222 25 : _request.removeCookie(_request.host().getSessionInfo().name);
223 25 : _valid = false;
224 25 : return true;
225 : }
226 :
227 25 : bool Session::touch(TimeInterval maxAge) {
228 25 : if (maxAge) {
229 25 : _maxAge = maxAge;
230 25 : setInteger(maxAge.toSeconds(), SA_SESSION_MAX_AGE_KEY);
231 : }
232 :
233 25 : return write();
234 : }
235 :
236 175 : db::User *Session::getUser() const {
237 175 : return _user;
238 : }
239 :
240 0 : TimeInterval Session::getMaxAge() const {
241 0 : return _maxAge;
242 : }
243 :
244 :
245 0 : Value Session::getStorageData(Request &rctx, const Token &key) {
246 0 : Value ret;
247 0 : rctx.performWithStorage([&] (const db::Transaction &t) {
248 0 : ret = t.getAdapter().get(key);
249 0 : return true;
250 : });
251 0 : return ret;
252 0 : }
253 :
254 150 : Value Session::getStorageData(Request &rctx, const Bytes &key) {
255 150 : Value ret;
256 150 : rctx.performWithStorage([&] (const db::Transaction &t) {
257 150 : ret = t.getAdapter().get(key);
258 150 : return true;
259 : });
260 150 : return ret;
261 0 : }
262 :
263 50 : bool Session::setStorageData(Request &rctx, const Token &key, const Value &d, TimeInterval maxAge) {
264 50 : bool ret = false;
265 50 : rctx.performWithStorage([&] (const db::Transaction &t) {
266 50 : ret = t.getAdapter().set(key, d, maxAge);
267 50 : return true;
268 : });
269 50 : return ret;
270 : }
271 :
272 25 : bool Session::clearStorageData(Request &rctx, const Token &key) {
273 25 : bool ret = false;
274 25 : rctx.performWithStorage([&] (const db::Transaction &t) {
275 25 : ret = t.getAdapter().clear(key);
276 25 : return true;
277 : });
278 25 : return ret;
279 : }
280 :
281 150 : db::User *Session::getStorageUser(Request &rctx, uint64_t oid) {
282 150 : db::User *ret = nullptr;
283 150 : rctx.performWithStorage([&] (const db::Transaction &t) {
284 150 : ret = db::User::get(t, oid);
285 150 : return true;
286 : });
287 150 : return ret;
288 : }
289 :
290 : }
|