1 /*MT*
2 
3     MediaTomb - http://www.mediatomb.cc/
4 
5     auth.cc - this file is part of MediaTomb.
6 
7     Copyright (C) 2005 Gena Batyan <bgeradz@mediatomb.cc>,
8                        Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>
9 
10     Copyright (C) 2006-2010 Gena Batyan <bgeradz@mediatomb.cc>,
11                             Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>,
12                             Leonhard Wimmer <leo@mediatomb.cc>
13 
14     MediaTomb is free software; you can redistribute it and/or modify
15     it under the terms of the GNU General Public License version 2
16     as published by the Free Software Foundation.
17 
18     MediaTomb is distributed in the hope that it will be useful,
19     but WITHOUT ANY WARRANTY; without even the implied warranty of
20     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21     GNU General Public License for more details.
22 
23     You should have received a copy of the GNU General Public License
24     version 2 along with MediaTomb; if not, write to the Free Software
25     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
26 
27     $Id$
28 */
29 
30 /// \file auth.cc
31 
32 #include "pages.h" // API
33 
34 #include <chrono>
35 
36 #include "config/config_manager.h"
37 #include "session_manager.h"
38 #include "util/tools.h"
39 
40 static constexpr auto LOGIN_TIMEOUT = std::chrono::seconds(10);
41 
get_time()42 static std::chrono::seconds get_time()
43 {
44     return std::chrono::time_point_cast<std::chrono::seconds>(std::chrono::system_clock::now()).time_since_epoch();
45 }
46 
generate_token()47 static std::string generate_token()
48 {
49     auto expiration = get_time() + LOGIN_TIMEOUT;
50     std::string salt = generateRandomId();
51     return fmt::format("{}_{}", expiration.count(), salt);
52 }
53 
check_token(const std::string & token,const std::string & password,const std::string & encPassword)54 static bool check_token(const std::string& token, const std::string& password, const std::string& encPassword)
55 {
56     std::vector<std::string> parts = splitString(token, '_');
57     if (parts.size() != 2)
58         return false;
59     auto expiration = std::chrono::seconds(std::stol(parts[0]));
60     if (expiration < get_time())
61         return false;
62     std::string checksum = hexStringMd5(token + password);
63     return (checksum == encPassword);
64 }
65 
Auth(std::shared_ptr<ContentManager> content)66 Web::Auth::Auth(std::shared_ptr<ContentManager> content)
67     : WebRequestHandler(std::move(content))
68     , timeout(std::chrono::seconds(60 * config->getIntOption(CFG_SERVER_UI_SESSION_TIMEOUT)))
69 {
70 }
71 
process()72 void Web::Auth::process()
73 {
74     std::string action = param("action");
75     auto root = xmlDoc->document_element();
76 
77     if (action.empty()) {
78         root.append_child("error").append_child(pugi::node_pcdata).set_value("req_type auth: no action given");
79         return;
80     }
81 
82     if (action == "get_config") {
83         auto cfg = root.append_child("config");
84         cfg.append_attribute("accounts") = accountsEnabled();
85         cfg.append_attribute("show-tooltips") = config->getBoolOption(CFG_SERVER_UI_SHOW_TOOLTIPS);
86         cfg.append_attribute("poll-when-idle") = config->getBoolOption(CFG_SERVER_UI_POLL_WHEN_IDLE);
87         cfg.append_attribute("poll-interval") = config->getIntOption(CFG_SERVER_UI_POLL_INTERVAL);
88 
89         /// CREATE XML FRAGMENT FOR ITEMS PER PAGE
90         auto ipp = cfg.append_child("items-per-page");
91         xml2JsonHints->setArrayName(ipp, "option");
92         ipp.append_attribute("default") = config->getIntOption(CFG_SERVER_UI_DEFAULT_ITEMS_PER_PAGE);
93 
94         auto menu_opts = config->getArrayOption(CFG_SERVER_UI_ITEMS_PER_PAGE_DROPDOWN);
95         for (auto&& menu_opt : menu_opts) {
96             ipp.append_child("option").append_child(pugi::node_pcdata).set_value(menu_opt.c_str());
97         }
98 
99 #ifdef HAVE_INOTIFY
100         cfg.append_attribute("have-inotify") = config->getBoolOption(CFG_IMPORT_AUTOSCAN_USE_INOTIFY);
101 #else
102         cfg.append_attribute("have-inotify") = false;
103 #endif
104 
105         auto actions = cfg.append_child("actions");
106         xml2JsonHints->setArrayName(actions, "action");
107         //actions->appendTextChild("action", "fokel1");
108         //actions->appendTextChild("action", "fokel2");
109 
110         auto friendlyName = cfg.append_child("friendlyName").append_child(pugi::node_pcdata);
111         friendlyName.set_value(config->getOption(CFG_SERVER_NAME).c_str());
112 
113         auto gerberaVersion = cfg.append_child("version").append_child(pugi::node_pcdata);
114         gerberaVersion.set_value(GERBERA_VERSION);
115     } else if (action == "get_sid") {
116         log_debug("checking/getting sid...");
117         std::shared_ptr<Session> session;
118         std::string sid = param("sid");
119 
120         if (sid.empty() || !(session = sessionManager->getSession(sid))) {
121             session = sessionManager->createSession(timeout);
122             root.append_attribute("sid_was_valid") = false;
123         } else {
124             session->clearUpdateIDs();
125             root.append_attribute("sid_was_valid") = true;
126         }
127         root.append_attribute("sid") = session->getID().c_str();
128 
129         if (!session->isLoggedIn() && !accountsEnabled()) {
130             session->logIn();
131             //throw SessionException("not logged in");
132         }
133         root.append_attribute("logged_in") = session->isLoggedIn();
134     } else if (action == "logout") {
135         check_request();
136         std::string sid = param("sid");
137         auto session = sessionManager->getSession(sid);
138         if (!session)
139             throw_std_runtime_error("illegal session id");
140         sessionManager->removeSession(sid);
141     } else if (action == "get_token") {
142         check_request(false);
143 
144         // sending token
145         std::string token = generate_token();
146         session->put("token", token);
147         root.append_child("token").append_child(pugi::node_pcdata).set_value(token.c_str());
148     } else if (action == "login") {
149         check_request(false);
150 
151         // authentication
152         std::string username = param("username");
153         std::string encPassword = param("password");
154         std::string sid = param("sid");
155 
156         if (username.empty() || encPassword.empty())
157             throw LoginException("Missing username or password");
158 
159         auto session = sessionManager->getSession(sid);
160         if (!session)
161             throw_std_runtime_error("illegal session id");
162 
163         std::string correctPassword = sessionManager->getUserPassword(username);
164 
165         if (correctPassword.empty() || !check_token(session->get("token"), correctPassword, encPassword))
166             throw LoginException("Invalid username or password");
167 
168         session->logIn();
169     } else
170         throw_std_runtime_error("illegal action given to req_type auth");
171 }
172