FunkFeuer Node Manager
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.php 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. <?php
  2. /**
  3. * FunkFeuer Node Manager.
  4. *
  5. * @author Bernhard Froehlich <decke@bluelife.at>
  6. * @copyright 2017 Bernhard Froehlich
  7. * @license BSD License (2 Clause)
  8. *
  9. * @link https://github.com/decke/nodeman
  10. */
  11. namespace FunkFeuer\Nodeman;
  12. require_once __DIR__.'/vendor/autoload.php';
  13. /* handle static files from php builtin webserver */
  14. if (php_sapi_name() == 'cli-server') {
  15. $basedir = dirname(__FILE__);
  16. $allowed_subdirs = array('/css/', '/js/', '/images/');
  17. $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
  18. $path = realpath($basedir.$uri);
  19. if ($path !== false && strpos($path, $basedir) === 0) {
  20. foreach ($allowed_subdirs as $dir) {
  21. if (strpos($path, $basedir.$dir) === 0) {
  22. return false;
  23. }
  24. }
  25. }
  26. }
  27. $session = new Session();
  28. $app = new \Slim\App();
  29. /* init php-view */
  30. $container = $app->getContainer();
  31. /* init flash messages */
  32. $container['flash'] = function () {
  33. return new \Slim\Flash\Messages();
  34. };
  35. /* init twig-view */
  36. $container['view'] = function ($container) use ($session) {
  37. $renderer = new \Slim\Views\Twig(__DIR__.'/templates/', array(
  38. 'cache' => false,
  39. 'debug' => true
  40. // 'cache' => Config::get('cache.directory')
  41. ));
  42. $env = $renderer->getEnvironment();
  43. $env->addExtension(new \Twig_Extension_Debug());
  44. $env->addGlobal('nonce', bin2hex(random_bytes(5)));
  45. $env->addGlobal('session', $session);
  46. $env->addGlobal('config', new \FunkFeuer\Nodeman\Config());
  47. $env->addGlobal('flash', $container->get('flash'));
  48. $router = $container->get('router');
  49. $uri = \Slim\Http\Uri::createFromEnvironment(new \Slim\Http\Environment($_SERVER));
  50. $renderer->addExtension(new \Slim\Views\TwigExtension($router, $uri));
  51. return $renderer;
  52. };
  53. /* Middlewares */
  54. $app->add(function ($request, $response, $next) {
  55. /* CSP and other security headers */
  56. if (!$response->hasHeader('Content-Security-Policy')) {
  57. $globals = $this->view->getEnvironment()->getGlobals();
  58. $response = $response->withHeader('Content-Security-Policy', "script-src 'strict-dynamic' 'nonce-".$globals['nonce']."' 'unsafe-inline' http: https:; object-src 'none'; font-src 'self'; base-uri 'none'; frame-ancestors 'none';");
  59. }
  60. return $next($request, $response);
  61. });
  62. /* landing page */
  63. $app->get('/', function ($request, $response) {
  64. return $this->view->render($response, 'index.html');
  65. });
  66. /* Authentication - Login */
  67. $app->post('/login', function ($request, $response) use ($session) {
  68. if (!$request->getParam('email') || !$request->getParam('password')) {
  69. $this->flash->addMessage('error', 'Authentication failed');
  70. } elseif (!$session->login($request->getParam('email'), $request->getParam('password'))) {
  71. $this->flash->addMessage('error', 'Authentication failed');
  72. }
  73. return $response->withStatus(302)->withHeader('Location', '/');
  74. });
  75. $app->get('/logout', function ($request, $response) use ($session) {
  76. $session->logout();
  77. return $response->withStatus(302)->withHeader('Location', '/');
  78. });
  79. /* Registration */
  80. $app->get('/register', function ($request, $response) {
  81. return $this->view->render($response, 'register.html');
  82. });
  83. $app->post('/register', function ($request, $response) {
  84. if (!filter_var($request->getParam('email'), FILTER_VALIDATE_EMAIL)) {
  85. $this->flash->addMessageNow('error', 'EMail address invalid');
  86. }
  87. if (strlen($request->getParam('email')) > 50) {
  88. $this->flash->addMessageNow('error', 'EMail address too short (max length 50)');
  89. }
  90. if (strlen($request->getParam('password1')) < 6) {
  91. $this->flash->addMessageNow('error', 'Password too short (min length 6)');
  92. }
  93. if ($request->getParam('password1') != $request->getParam('password2')) {
  94. $this->flash->addMessageNow('error', 'Passwords do not match');
  95. }
  96. $user = new User();
  97. if ($user->emailExists($request->getParam('email'))) {
  98. $this->flash->addMessageNow('error', 'EMail address already in use');
  99. }
  100. if (!$this->flash->hasMessage('error')) {
  101. $user = new User();
  102. $user->setPassword($request->getParam('password1'));
  103. $user->email = $request->getParam('email');
  104. $user->firstname = $request->getParam('firstname');
  105. $user->lastname = $request->getParam('lastname');
  106. $user->phone = $request->getParam('phone');
  107. $user->usergroup = 'user';
  108. if ($user->save()) {
  109. $this->flash->addMessage('success', 'Account created');
  110. return $response->withStatus(302)->withHeader('Location', '/');
  111. } else {
  112. $this->flash->addMessageNow('error', 'Account creation failed');
  113. }
  114. }
  115. $data = array(
  116. 'email' => $request->getParam('email'),
  117. 'firstname' => $request->getParam('firstname'),
  118. 'lastname' => $request->getParam('lastname'),
  119. 'phone' => $request->getParam('phone')
  120. );
  121. return $this->view->render($response, 'register.html', array('data' => $data));
  122. });
  123. /* Map */
  124. $app->get('/map', function ($request, $response) {
  125. $query = '';
  126. if ($request->getParam('lat') && $request->getParam('lng')) {
  127. $query = sprintf('?lat=%f&lng=%f', $request->getParam('lat'), $request->getParam('lng'));
  128. }
  129. return $this->view->render($response, 'map.html', array(
  130. 'css' => array('/css/leaflet.css'),
  131. 'js' => array('/js/leaflet.min.js', '/mapdata'.$query)
  132. ));
  133. });
  134. $app->get('/mapdata', function ($request, $response) {
  135. $links = array();
  136. $location = new Location();
  137. $locations = array();
  138. $deflocation = array();
  139. if ($request->getParam('lat') && $request->getParam('lng')) {
  140. $deflocation['lat'] = $request->getParam('lat');
  141. $deflocation['lng'] = $request->getParam('lng');
  142. }
  143. foreach ($location->getAllLocations(null, 0, 999999) as $loc) {
  144. $popup = sprintf('<b>%s</b><br>%s', $loc->name, $loc->address);
  145. if (strlen($loc->gallerylink)) {
  146. $popup .= sprintf('<br><a href=\"%s\">Gallery</a>', $loc->gallerylink);
  147. }
  148. $locations[] = array(
  149. 'name' => $loc->name,
  150. 'type' => $loc->status,
  151. 'location' => $loc->getLongLat(),
  152. 'popup' => $popup
  153. );
  154. }
  155. $link = new InterfaceLink();
  156. foreach ($link->getAllLinks() as $link) {
  157. $fromloc = $link->getFromLocation();
  158. $toloc = $link->getToLocation();
  159. $links[] = array(
  160. 'from' => $fromloc->getLongLat(),
  161. 'to' => $toloc->getLongLat(),
  162. 'quality' => $link->quality
  163. );
  164. }
  165. return $this->view->render($response, 'map.js', array(
  166. 'deflocation' => $deflocation,
  167. 'locations' => $locations,
  168. 'links' => $links
  169. ))->withHeader('Content-Type', 'application/javascript; charset=utf-8');
  170. });
  171. /* Locations */
  172. $app->get('/locations', function ($request, $response) {
  173. $loc = new Location();
  174. return $this->view->render($response, 'locations.html', array(
  175. 'locations' => $loc->getAllLocations(null, 0, 999999)
  176. ));
  177. });
  178. $app->get('/location/add', function ($request, $response) use ($session) {
  179. if (!$session->isAuthenticated()) {
  180. $this->flash->addMessage('error', 'Please login first');
  181. return $response->withStatus(302)->withHeader('Location', '/');
  182. }
  183. return $this->view->render($response, 'location/add.html', array(
  184. 'css' => array('/css/leaflet.css'),
  185. 'js' => array('/js/leaflet.min.js', '/js/grazmap.js')
  186. ));
  187. });
  188. $app->post('/location/add', function ($request, $response) use ($session) {
  189. if (!$session->isAuthenticated()) {
  190. $this->flash->addMessageNow('error', 'Please login first');
  191. return $response->withStatus(302)->withHeader('Location', '/');
  192. }
  193. if (!preg_match('/^[0-9A-Za-z]{3,50}$/', $request->getParam('name'))) {
  194. $this->flash->addMessageNow('error', 'Location name is invalid. Length from 3-50. Allowed characters only 0-9, A-Z, a-z');
  195. }
  196. if (strlen($request->getParam('address')) < 5) {
  197. $this->flash->addMessageNow('error', 'Address is invalid');
  198. }
  199. if (strlen($request->getParam('address')) > 255) {
  200. $this->flash->addMessageNow('error', 'Address too long (max length 255)');
  201. }
  202. if (!$request->getParam('latitude') || !$request->getParam('longitude')) {
  203. $this->flash->addMessageNow('error', 'Position on map is missing');
  204. }
  205. $location = new Location();
  206. if ($location->loadByName($request->getParam('name'))) {
  207. $this->flash->addMessageNow('error', 'Location name already exists');
  208. }
  209. /* HACK: Slim-Flash hasMessage('error') does not see messages for next request */
  210. if (!isset($_SESSION['slimFlash']['error'])) {
  211. $location = new Location();
  212. $location->name = $request->getParam('name');
  213. $location->owner = $session->getUser()->userid;
  214. $location->address = $request->getParam('address');
  215. $location->latitude = $request->getParam('latitude');
  216. $location->longitude = $request->getParam('longitude');
  217. $location->status = 'offline';
  218. $location->gallerylink = '';
  219. $location->description = '';
  220. if ($location->save()) {
  221. $this->flash->addMessage('success', 'Location created');
  222. return $response->withStatus(302)->withHeader('Location', '/');
  223. } else {
  224. $this->flash->addMessageNow('error', 'Location creation failed');
  225. }
  226. }
  227. $data = array(
  228. 'name' => $request->getParam('name'),
  229. 'address' => $request->getParam('address')
  230. );
  231. return $this->view->render($response, 'location/add.html', array(
  232. 'data' => $data,
  233. 'css' => array('/css/leaflet.css'),
  234. 'js' => array('/js/leaflet.min.js', '/js/grazmap.js')
  235. ));
  236. });
  237. /* Nodes */
  238. $app->get('/location/{locationid}/add', function ($request, $response, $args) use ($session) {
  239. if (!$session->isAuthenticated()) {
  240. $this->flash->addMessage('error', 'Please login first');
  241. return $response->withStatus(302)->withHeader('Location', '/');
  242. }
  243. return $this->view->render($response, 'location/node/add.html', array(
  244. 'data' => array('locationid' => $args['locationid'])
  245. ));
  246. });
  247. $app->post('/location/{locationid}/add', function ($request, $response, $args) use ($session) {
  248. if (!$session->isAuthenticated()) {
  249. $this->flash->addMessage('error', 'Please login first');
  250. return $response->withStatus(302)->withHeader('Location', '/');
  251. }
  252. $location = new Location($args['locationid']);
  253. if ($location->owner != $session->getUser()->userid) {
  254. $this->flash->addMessage('error', 'Permission denied');
  255. return $response->withStatus(302)->withHeader('Location', '/');
  256. }
  257. if (!preg_match('/^[0-9A-Za-z]{3,50}$/', $request->getParam('name'))) {
  258. $this->flash->addMessageNow('error', 'Node name is invalid. Length from 3-50. Allowed characters only 0-9, A-Z, a-z');
  259. }
  260. if (strlen($request->getParam('description')) > 16384) {
  261. $this->flash->addMessageNow('error', 'Description is too long (max 16K)');
  262. }
  263. $location = new Location($args['locationid']);
  264. if ($location->nodeExists($request->getParam('name'))) {
  265. $this->flash->addMessageNow('error', 'Node name already exists');
  266. }
  267. /* HACK: Slim-Flash hasMessage('error') does not see messages for next request */
  268. if (!isset($_SESSION['slimFlash']['error'])) {
  269. $node = new Node();
  270. $node->name = $request->getParam('name');
  271. $node->owner = $session->getUser()->userid;
  272. $node->location = $args['locationid'];
  273. $node->hardware = 0;
  274. $node->description = $request->getParam('description');
  275. if ($node->save()) {
  276. $this->flash->addMessage('success', 'Node created');
  277. return $response->withStatus(302)->withHeader('Location', '/location/'.$node->location.'/node/'.$node->nodeid.'/');
  278. } else {
  279. $this->flash->addMessageNow('error', 'Location creation failed');
  280. }
  281. }
  282. $data = array(
  283. 'name' => $request->getParam('name'),
  284. 'description' => $request->getParam('description'),
  285. 'locationid' => $args['locationid']
  286. );
  287. return $this->view->render($response, 'location/node/add.html', array(
  288. 'data' => $data
  289. ));
  290. });
  291. /* User Area */
  292. $app->get('/user/home', function ($request, $response) use ($session) {
  293. if (!$session->isAuthenticated()) {
  294. $this->flash->addMessage('error', 'Please login first');
  295. return $response->withStatus(302)->withHeader('Location', '/');
  296. }
  297. $loc = new Location();
  298. return $this->view->render($response, 'user/home.html', array(
  299. 'user' => $session->getUser(),
  300. 'locations' => $loc->getAllLocations($session->getUser()->userid, 0, 999999)
  301. ));
  302. });
  303. $app->run();