diff --git a/LICENSE.md b/LICENSE.md index 63a11e3..56351c0 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,19 +1,7 @@ -Copyright (c) 2018 Netsyms Technologies. +Copyright (c) 2017-2019 Netsyms Technologies. Some rights reserved. -If you modify and redistribute this project, you must replace the branding -assets with your own. - -The branding assets include: - * the application icon - * the Netsyms N punchcard logo - * the Netsyms for Business graph logo - -If you are unsure if your usage is allowed, please contact us: -https://netsyms.com/contact -legal@netsyms.com - -All other portions of this application, -unless otherwise noted (in comments, headers, etc), are licensed as follows: +Licensed under the Mozilla Public License Version 2.0. Files without MPL header +comments, including third party code, may be under a different license. Mozilla Public License Version 2.0 ================================== diff --git a/README.md b/README.md index 9537d45..5e1aa7e 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,4 @@ Installing 8. Set the URL of this app ("URL") 9. Copy webroot.htaccess to your webroot and adjust paths if needed 10. Run `composer install` (or `composer.phar install`) to install dependency libraries -11. Run `git submodule init` and `git submodule update` to install other dependencies via git. \ No newline at end of file +11. Run `git submodule init` and `git submodule update` to install other dependencies via git. diff --git a/action.php b/action.php index 6fcbb4b..830127f 100644 --- a/action.php +++ b/action.php @@ -22,11 +22,11 @@ if ($VARS['action'] !== "signout") { */ function returnToSender($msg, $arg = "") { global $VARS; - if ($arg == "") { - header("Location: app.php?page=" . urlencode($VARS['source']) . "&msg=" . $msg); - } else { - header("Location: app.php?page=" . urlencode($VARS['source']) . "&msg=$msg&arg=$arg"); + $header = "Location: app.php?page=" . urlencode($VARS['source']) . "&msg=$msg"; + if ($arg != "") { + $header .= "&arg=$arg"; } + header($header); die(); } @@ -242,8 +242,8 @@ switch ($VARS['action']) { if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_FILES")) { returnToSender("no_permission"); } - $destpath = FILE_UPLOAD_PATH . $VARS['path']; - if (strpos(realpath($destpath), FILE_UPLOAD_PATH) !== 0) { + $destpath = $SETTINGS["file_upload_path"] . $VARS['path']; + if (strpos(realpath($destpath), $SETTINGS["file_upload_path"]) !== 0) { returnToSender("file_security_error"); } if (!file_exists($destpath) || !is_dir($destpath)) { @@ -315,7 +315,7 @@ switch ($VARS['action']) { returnToSender("no_permission"); } $foldername = preg_replace("/[^a-z0-9_\-]/", "_", strtolower($VARS['folder'])); - $newfolder = FILE_UPLOAD_PATH . $VARS['path'] . '/' . $foldername; + $newfolder = $SETTINGS["file_upload_path"] . $VARS['path'] . '/' . $foldername; if (mkdir($newfolder, 0755)) { returnToSender("folder_created", "&path=" . $VARS['path']); @@ -326,15 +326,15 @@ switch ($VARS['action']) { if (!$user->hasPermission("SITEWRITER") && !$user->hasPermission("SITEWRITER_FILES")) { returnToSender("no_permission"); } - $file = FILE_UPLOAD_PATH . $VARS['file']; - if (strpos(realpath($file), FILE_UPLOAD_PATH) !== 0) { + $file = $SETTINGS["file_upload_path"] . $VARS['file']; + if (strpos(realpath($file), $SETTINGS["file_upload_path"]) !== 0) { returnToSender("file_security_error"); } if (!file_exists($file)) { // Either way the file is gone returnToSender("file_deleted"); } - if (!is_writable($file) || realpath($file) == realpath(FILE_UPLOAD_PATH)) { + if (!is_writable($file) || realpath($file) == realpath($SETTINGS["file_upload_path"])) { returnToSender("undeletable_file"); } if (is_dir($file)) { @@ -350,9 +350,9 @@ switch ($VARS['action']) { break; case "unsplash_download": Crew\Unsplash\HttpClient::init([ - 'applicationId' => UNSPLASH_ACCESSKEY, - 'secret' => UNSPLASH_SECRETKEY, - 'utmSource' => UNSPLASH_UTMSOURCE + 'applicationId' => $SETTINGS["unsplash"]["accesskey"], + 'secret' => $SETTINGS["unsplash"]["secretkey"], + 'utmSource' => $SETTINGS["unsplash"]["utmsource"] ]); Crew\Unsplash\Photo::find($VARS['imageid'])->download(); header('Content-Type: application/json'); @@ -360,6 +360,6 @@ switch ($VARS['action']) { break; case "signout": session_destroy(); - header('Location: index.php'); + header('Location: index.php?logout=1'); die("Logged out."); } \ No newline at end of file diff --git a/api.php b/api.php index 03178ea..d68b923 100644 --- a/api.php +++ b/api.php @@ -4,35 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/** - * Simple JSON API to allow other apps to access data from this app. - * - * Requests can be sent via either GET or POST requests. POST is recommended - * as it has a lower chance of being logged on the server, exposing unencrypted - * user passwords. - */ -require __DIR__ . '/required.php'; -header("Content-Type: application/json"); -$username = $VARS['username']; -$password = $VARS['password']; -$user = User::byUsername($username); -if ($user->exists() !== true || Login::auth($username, $password) !== Login::LOGIN_OK) { - header("HTTP/1.1 403 Unauthorized"); - die("\"403 Unauthorized\""); -} - -// query max results -$max = 20; -if (preg_match("/^[0-9]+$/", $VARS['max']) === 1 && $VARS['max'] <= 1000) { - $max = (int) $VARS['max']; -} - -switch ($VARS['action']) { - case "ping": - $out = ["status" => "OK", "maxresults" => $max, "pong" => true]; - exit(json_encode($out)); - default: - header("HTTP/1.1 400 Bad Request"); - die("\"400 Bad Request\""); -} \ No newline at end of file +// Load in new API from legacy location (a.k.a. here) +require __DIR__ . "/api/index.php"; \ No newline at end of file diff --git a/api/.htaccess b/api/.htaccess new file mode 100644 index 0000000..9a4efe4 --- /dev/null +++ b/api/.htaccess @@ -0,0 +1,5 @@ +# Rewrite for Nextcloud Notes API + + RewriteEngine on + RewriteRule ([a-zA-Z0-9]+) index.php?action=$1 [PT] + \ No newline at end of file diff --git a/api/actions/ping.php b/api/actions/ping.php new file mode 100644 index 0000000..c764967 --- /dev/null +++ b/api/actions/ping.php @@ -0,0 +1,9 @@ + [ + "load" => "ping.php", + "vars" => [ + ] + ] +]; diff --git a/api/functions.php b/api/functions.php new file mode 100644 index 0000000..9357a53 --- /dev/null +++ b/api/functions.php @@ -0,0 +1,149 @@ + 5) { + for ($i = 2; $i < strlen($key) - 2; $i++) { + $resp[$i] = "*"; + } + } + return $resp; +} + +/** + * Check if the request is allowed + * @global array $VARS + * @return bool true if the request should continue, false if the request is bad + */ +function authenticate(): bool { + global $VARS, $SETTINGS; + // HTTP basic auth + if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) { + $username = $_SERVER['PHP_AUTH_USER']; + $password = $_SERVER['PHP_AUTH_PW']; + } else if (!empty($VARS['username']) && !empty($VARS['password'])) { + $username = $VARS['username']; + $password = $VARS['password']; + } else { + return false; + } + $user = User::byUsername($username); + if (!$user->exists()) { + return false; + } + if ($user->checkPassword($password, true)) { + // Check that the user has permission to access the app + $perms = is_array($SETTINGS['api_permissions']) ? $SETTINGS['api_permissions'] : $SETTINGS['permissions']; + foreach ($perms as $perm) { + if (!$user->hasPermission($perm)) { + return false; + } + } + return true; + } + return false; +} + +/** + * Get the User whose credentials were used to make the request. + */ +function getRequestUser(): User { + global $VARS; + if (!empty($_SERVER['PHP_AUTH_USER'])) { + return User::byUsername($_SERVER['PHP_AUTH_USER']); + } else { + return User::byUsername($VARS['username']); + } +} + +function checkVars($vars, $or = false) { + global $VARS; + $ok = []; + foreach ($vars as $key => $val) { + if (strpos($key, "OR") === 0) { + checkVars($vars[$key], true); + continue; + } + + // Only check type of optional variables if they're set, and don't + // mark them as bad if they're not set + if (strpos($key, " (optional)") !== false) { + $key = str_replace(" (optional)", "", $key); + if (empty($VARS[$key])) { + continue; + } + } else { + if (empty($VARS[$key])) { + $ok[$key] = false; + continue; + } + } + + if (strpos($val, "/") === 0) { + // regex + $ok[$key] = preg_match($val, $VARS[$key]) === 1; + } else { + $checkmethod = "is_$val"; + $ok[$key] = !($checkmethod($VARS[$key]) !== true); + } + } + if ($or) { + $success = false; + $bad = ""; + foreach ($ok as $k => $v) { + if ($v) { + $success = true; + break; + } else { + $bad = $k; + } + } + if (!$success) { + http_response_code(400); + die("400 Bad request: variable $bad is missing or invalid"); + } + } else { + foreach ($ok as $key => $bool) { + if (!$bool) { + http_response_code(400); + die("400 Bad request: variable $key is missing or invalid"); + } + } + } +} diff --git a/api/index.php b/api/index.php new file mode 100644 index 0000000..23cb28c --- /dev/null +++ b/api/index.php @@ -0,0 +1,81 @@ += 1) { + $VARS["action"] = $route[0]; + } + if (count($route) >= 2 && strpos($route[1], "?") !== 0) { + for ($i = 1; $i < count($route); $i++) { + if (empty($route[$i]) || strpos($route[$i], "=") === false) { + continue; + } + $key = explode("=", $route[$i], 2)[0]; + $val = explode("=", $route[$i], 2)[1]; + $VARS[$key] = $val; + } + } + + if (strpos($route[count($route) - 1], "?") === 0) { + $morevars = explode("&", substr($route[count($route) - 1], 1)); + foreach ($morevars as $var) { + $key = explode("=", $var, 2)[0]; + $val = explode("=", $var, 2)[1]; + $VARS[$key] = $val; + } + } +} + +if (!authenticate()) { + header('WWW-Authenticate: Basic realm="' . $SETTINGS['site_title'] . '"'); + header('HTTP/1.1 401 Unauthorized'); + die("401 Unauthorized: you need to supply valid credentials."); +} + +if (empty($VARS['action'])) { + http_response_code(404); + die("404 No action specified"); +} + +if (!isset($APIS[$VARS['action']])) { + http_response_code(404); + die("404 Action not defined"); +} + +$APIACTION = $APIS[$VARS["action"]]; + +if (!file_exists(__DIR__ . "/actions/" . $APIACTION["load"])) { + http_response_code(404); + die("404 Action not found"); +} + +if (!empty($APIACTION["vars"])) { + checkVars($APIACTION["vars"]); +} + +require_once __DIR__ . "/actions/" . $APIACTION["load"]; diff --git a/app.php b/app.php index 7f4dac8..5ca0dfe 100644 --- a/app.php +++ b/app.php @@ -39,7 +39,7 @@ header("Link: ; rel=preload; as=script", fals - + @@ -127,7 +127,7 @@ END; - + @@ -163,7 +163,7 @@ END; - + @@ -183,8 +183,8 @@ END; ?> diff --git a/index.php b/index.php index 1f8f76f..1bdd70d 100644 --- a/index.php +++ b/index.php @@ -1,175 +1,131 @@ get("no access permission", false); -} +/** + * Show a simple HTML page with a line of text and a button. Matches the UI of + * the AccountHub login flow. + * + * @global type $SETTINGS + * @global type $SECURE_NONCE + * @global type $Strings + * @param string $title Text to show, passed through i18n + * @param string $button Button text, passed through i18n + * @param string $url URL for the button + */ +function showHTML(string $title, string $button, string $url) { + global $SETTINGS, $SECURE_NONCE, $Strings; + ?> + + + + -/* Authenticate user */ -$userpass_ok = false; -$multiauth = false; -if (Login::checkLoginServer()) { - if (empty($VARS['progress'])) { - // Easy way to remove "undefined" warnings. - } else if ($VARS['progress'] == "1") { - if (!CAPTCHA_ENABLED || (CAPTCHA_ENABLED && Login::verifyCaptcha($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], CAPTCHA_SERVER . "/api.php"))) { - $autherror = ""; - $user = User::byUsername($VARS['username']); - if ($user->exists()) { - $status = $user->getStatus()->getString(); - switch ($status) { - case "LOCKED_OR_DISABLED": - $alert = $Strings->get("account locked", false); - break; - case "TERMINATED": - $alert = $Strings->get("account terminated", false); - break; - case "CHANGE_PASSWORD": - $alert = $Strings->get("password expired", false); - break; - case "NORMAL": - $username_ok = true; - break; - case "ALERT_ON_ACCESS": - $mail_resp = $user->sendAlertEmail(); - if (DEBUG) { - var_dump($mail_resp); - } - $username_ok = true; - break; - default: - if (!empty($error)) { - $alert = $error; - } else { - $alert = $Strings->get("login error", false); - } - break; - } - if ($username_ok) { - if ($user->checkPassword($VARS['password'])) { - $_SESSION['passok'] = true; // stop logins using only username and authcode - if ($user->has2fa()) { - $multiauth = true; - } else { - Session::start($user); - header('Location: app.php'); - die("Logged in, go to app.php"); - } - } else { - $alert = $Strings->get("login incorrect", false); - } - } - } else { // User does not exist anywhere - $alert = $Strings->get("login incorrect", false); - } - } else { - $alert = $Strings->get("captcha error", false); + + + + + + - - - - - - - - - - - - + - - + + - - - - - get("sign in"); ?> - - - - - - - " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /> - " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /> - - - - - - - - get("2fa prompt"); ?> - - " required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /> - - - - - get("continue"); ?> - - + + get($title); ?> + + + + + + get($button); ?> + - - - - -