Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
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.

470 lines
13 KiB

11 years ago
4 years ago
11 years ago
10 years ago
11 years ago
11 years ago
  1. <?php
  2. use Friendica\App;
  3. /**
  4. * @brief Calculate the hash that is needed for the "Friendica" cookie
  5. *
  6. * @param array $user Record from "user" table
  7. *
  8. * @return string Hashed data
  9. */
  10. function cookie_hash($user) {
  11. return(hash("sha256", get_config("system", "site_prvkey").
  12. $user["uprvkey"].
  13. $user["password"]));
  14. }
  15. /**
  16. * @brief Set the "Friendica" cookie
  17. *
  18. * @param int $time
  19. * @param array $user Record from "user" table
  20. */
  21. function new_cookie($time, $user = array()) {
  22. if ($time != 0) {
  23. $time = $time + time();
  24. }
  25. if ($user) {
  26. $value = json_encode(array("uid" => $user["uid"],
  27. "hash" => cookie_hash($user),
  28. "ip" => $_SERVER['REMOTE_ADDR']));
  29. }
  30. else {
  31. $value = "";
  32. }
  33. setcookie("Friendica", $value, $time, "/", "",
  34. (get_config('system', 'ssl_policy') == SSL_POLICY_FULL), true);
  35. }
  36. function authenticate_success($user_record, $login_initial = false, $interactive = false, $login_refresh = false) {
  37. $a = get_app();
  38. $_SESSION['uid'] = $user_record['uid'];
  39. $_SESSION['theme'] = $user_record['theme'];
  40. $_SESSION['mobile-theme'] = get_pconfig($user_record['uid'], 'system', 'mobile_theme');
  41. $_SESSION['authenticated'] = 1;
  42. $_SESSION['page_flags'] = $user_record['page-flags'];
  43. $_SESSION['my_url'] = App::get_baseurl() . '/profile/' . $user_record['nickname'];
  44. $_SESSION['my_address'] = $user_record['nickname'] . '@' . substr(App::get_baseurl(),strpos(App::get_baseurl(),'://')+3);
  45. $_SESSION['addr'] = $_SERVER['REMOTE_ADDR'];
  46. $a->user = $user_record;
  47. if($interactive) {
  48. if ($a->user['login_date'] <= NULL_DATE) {
  49. $_SESSION['return_url'] = 'profile_photo/new';
  50. $a->module = 'profile_photo';
  51. info( t("Welcome ") . $a->user['username'] . EOL);
  52. info( t('Please upload a profile photo.') . EOL);
  53. }
  54. else
  55. info( t("Welcome back ") . $a->user['username'] . EOL);
  56. }
  57. $member_since = strtotime($a->user['register_date']);
  58. if(time() < ($member_since + ( 60 * 60 * 24 * 14)))
  59. $_SESSION['new_member'] = true;
  60. else
  61. $_SESSION['new_member'] = false;
  62. if(strlen($a->user['timezone'])) {
  63. date_default_timezone_set($a->user['timezone']);
  64. $a->timezone = $a->user['timezone'];
  65. }
  66. $master_record = $a->user;
  67. if((x($_SESSION,'submanage')) && intval($_SESSION['submanage'])) {
  68. $r = q("select * from user where uid = %d limit 1",
  69. intval($_SESSION['submanage'])
  70. );
  71. if (dbm::is_result($r))
  72. $master_record = $r[0];
  73. }
  74. $r = q("SELECT `uid`,`username`,`nickname` FROM `user` WHERE `password` = '%s' AND `email` = '%s' AND `account_removed` = 0 ",
  75. dbesc($master_record['password']),
  76. dbesc($master_record['email'])
  77. );
  78. if (dbm::is_result($r))
  79. $a->identities = $r;
  80. else
  81. $a->identities = array();
  82. $r = q("select `user`.`uid`, `user`.`username`, `user`.`nickname`
  83. from manage INNER JOIN user on manage.mid = user.uid where `user`.`account_removed` = 0
  84. and `manage`.`uid` = %d",
  85. intval($master_record['uid'])
  86. );
  87. if (dbm::is_result($r))
  88. $a->identities = array_merge($a->identities,$r);
  89. if($login_initial)
  90. logger('auth_identities: ' . print_r($a->identities,true), LOGGER_DEBUG);
  91. if($login_refresh)
  92. logger('auth_identities refresh: ' . print_r($a->identities,true), LOGGER_DEBUG);
  93. $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 1 LIMIT 1",
  94. intval($_SESSION['uid']));
  95. if (dbm::is_result($r)) {
  96. $a->contact = $r[0];
  97. $a->cid = $r[0]['id'];
  98. $_SESSION['cid'] = $a->cid;
  99. }
  100. header('X-Account-Management-Status: active; name="' . $a->user['username'] . '"; id="' . $a->user['nickname'] .'"');
  101. if($login_initial || $login_refresh) {
  102. q("UPDATE `user` SET `login_date` = '%s' WHERE `uid` = %d",
  103. dbesc(datetime_convert()),
  104. intval($_SESSION['uid'])
  105. );
  106. // Set the login date for all identities of the user
  107. q("UPDATE `user` SET `login_date` = '%s' WHERE `password` = '%s' AND `email` = '%s' AND `account_removed` = 0",
  108. dbesc(datetime_convert()),
  109. dbesc($master_record['password']),
  110. dbesc($master_record['email'])
  111. );
  112. }
  113. if ($login_initial) {
  114. // If the user specified to remember the authentication, then set a cookie
  115. // that expires after one week (the default is when the browser is closed).
  116. // The cookie will be renewed automatically.
  117. // The week ensures that sessions will expire after some inactivity.
  118. if ($_SESSION['remember']) {
  119. logger('Injecting cookie for remembered user '. $_SESSION['remember_user']['nickname']);
  120. new_cookie(604800, $user_record);
  121. unset($_SESSION['remember']);
  122. }
  123. }
  124. if ($login_initial) {
  125. call_hooks('logged_in', $a->user);
  126. if (($a->module !== 'home') && isset($_SESSION['return_url'])) {
  127. goaway(App::get_baseurl() . '/' . $_SESSION['return_url']);
  128. }
  129. }
  130. }
  131. function can_write_wall(App $a, $owner) {
  132. static $verified = 0;
  133. if ((! (local_user())) && (! (remote_user()))) {
  134. return false;
  135. }
  136. $uid = local_user();
  137. if (($uid) && ($uid == $owner)) {
  138. return true;
  139. }
  140. if (remote_user()) {
  141. // use remembered decision and avoid a DB lookup for each and every display item
  142. // DO NOT use this function if there are going to be multiple owners
  143. // We have a contact-id for an authenticated remote user, this block determines if the contact
  144. // belongs to this page owner, and has the necessary permissions to post content
  145. if ($verified === 2) {
  146. return true;
  147. } elseif ($verified === 1) {
  148. return false;
  149. } else {
  150. $cid = 0;
  151. if (is_array($_SESSION['remote'])) {
  152. foreach ($_SESSION['remote'] as $visitor) {
  153. if ($visitor['uid'] == $owner) {
  154. $cid = $visitor['cid'];
  155. break;
  156. }
  157. }
  158. }
  159. if (! $cid) {
  160. return false;
  161. }
  162. $r = q("SELECT `contact`.*, `user`.`page-flags` FROM `contact` INNER JOIN `user` on `user`.`uid` = `contact`.`uid`
  163. WHERE `contact`.`uid` = %d AND `contact`.`id` = %d AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  164. AND `user`.`blockwall` = 0 AND `readonly` = 0 AND ( `contact`.`rel` IN ( %d , %d ) OR `user`.`page-flags` = %d ) LIMIT 1",
  165. intval($owner),
  166. intval($cid),
  167. intval(CONTACT_IS_SHARING),
  168. intval(CONTACT_IS_FRIEND),
  169. intval(PAGE_COMMUNITY)
  170. );
  171. if (dbm::is_result($r)) {
  172. $verified = 2;
  173. return true;
  174. }
  175. else {
  176. $verified = 1;
  177. }
  178. }
  179. }
  180. return false;
  181. }
  182. function permissions_sql($owner_id,$remote_verified = false,$groups = null) {
  183. $local_user = local_user();
  184. $remote_user = remote_user();
  185. /**
  186. * Construct permissions
  187. *
  188. * default permissions - anonymous user
  189. */
  190. $sql = " AND allow_cid = ''
  191. AND allow_gid = ''
  192. AND deny_cid = ''
  193. AND deny_gid = ''
  194. ";
  195. /**
  196. * Profile owner - everything is visible
  197. */
  198. if(($local_user) && ($local_user == $owner_id)) {
  199. $sql = '';
  200. }
  201. /**
  202. * Authenticated visitor. Unless pre-verified,
  203. * check that the contact belongs to this $owner_id
  204. * and load the groups the visitor belongs to.
  205. * If pre-verified, the caller is expected to have already
  206. * done this and passed the groups into this function.
  207. */
  208. elseif($remote_user) {
  209. if(! $remote_verified) {
  210. $r = q("SELECT id FROM contact WHERE id = %d AND uid = %d AND blocked = 0 LIMIT 1",
  211. intval($remote_user),
  212. intval($owner_id)
  213. );
  214. if (dbm::is_result($r)) {
  215. $remote_verified = true;
  216. $groups = init_groups_visitor($remote_user);
  217. }
  218. }
  219. if($remote_verified) {
  220. $gs = '<<>>'; // should be impossible to match
  221. if(is_array($groups) && count($groups)) {
  222. foreach($groups as $g)
  223. $gs .= '|<' . intval($g) . '>';
  224. }
  225. /*$sql = sprintf(
  226. " AND ( allow_cid = '' OR allow_cid REGEXP '<%d>' )
  227. AND ( deny_cid = '' OR NOT deny_cid REGEXP '<%d>' )
  228. AND ( allow_gid = '' OR allow_gid REGEXP '%s' )
  229. AND ( deny_gid = '' OR NOT deny_gid REGEXP '%s')
  230. ",
  231. intval($remote_user),
  232. intval($remote_user),
  233. dbesc($gs),
  234. dbesc($gs)
  235. );*/
  236. $sql = sprintf(
  237. " AND ( NOT (deny_cid REGEXP '<%d>' OR deny_gid REGEXP '%s')
  238. AND ( allow_cid REGEXP '<%d>' OR allow_gid REGEXP '%s' OR ( allow_cid = '' AND allow_gid = '') )
  239. )
  240. ",
  241. intval($remote_user),
  242. dbesc($gs),
  243. intval($remote_user),
  244. dbesc($gs)
  245. );
  246. }
  247. }
  248. return $sql;
  249. }
  250. function item_permissions_sql($owner_id,$remote_verified = false,$groups = null) {
  251. $local_user = local_user();
  252. $remote_user = remote_user();
  253. /**
  254. * Construct permissions
  255. *
  256. * default permissions - anonymous user
  257. */
  258. $sql = " AND `item`.allow_cid = ''
  259. AND `item`.allow_gid = ''
  260. AND `item`.deny_cid = ''
  261. AND `item`.deny_gid = ''
  262. AND `item`.private = 0
  263. ";
  264. /**
  265. * Profile owner - everything is visible
  266. */
  267. if($local_user && ($local_user == $owner_id)) {
  268. $sql = '';
  269. }
  270. /**
  271. * Authenticated visitor. Unless pre-verified,
  272. * check that the contact belongs to this $owner_id
  273. * and load the groups the visitor belongs to.
  274. * If pre-verified, the caller is expected to have already
  275. * done this and passed the groups into this function.
  276. */
  277. elseif($remote_user) {
  278. if(! $remote_verified) {
  279. $r = q("SELECT id FROM contact WHERE id = %d AND uid = %d AND blocked = 0 LIMIT 1",
  280. intval($remote_user),
  281. intval($owner_id)
  282. );
  283. if (dbm::is_result($r)) {
  284. $remote_verified = true;
  285. $groups = init_groups_visitor($remote_user);
  286. }
  287. }
  288. if($remote_verified) {
  289. $gs = '<<>>'; // should be impossible to match
  290. if(is_array($groups) && count($groups)) {
  291. foreach($groups as $g)
  292. $gs .= '|<' . intval($g) . '>';
  293. }
  294. $sql = sprintf(
  295. /*" AND ( private = 0 OR ( private in (1,2) AND wall = 1 AND ( allow_cid = '' OR allow_cid REGEXP '<%d>' )
  296. AND ( deny_cid = '' OR NOT deny_cid REGEXP '<%d>' )
  297. AND ( allow_gid = '' OR allow_gid REGEXP '%s' )
  298. AND ( deny_gid = '' OR NOT deny_gid REGEXP '%s')))
  299. ",
  300. intval($remote_user),
  301. intval($remote_user),
  302. dbesc($gs),
  303. dbesc($gs)
  304. */
  305. " AND ( `item`.private = 0 OR ( `item`.private in (1,2) AND `item`.`wall` = 1
  306. AND ( NOT (`item`.deny_cid REGEXP '<%d>' OR `item`.deny_gid REGEXP '%s')
  307. AND ( `item`.allow_cid REGEXP '<%d>' OR `item`.allow_gid REGEXP '%s' OR ( `item`.allow_cid = '' AND `item`.allow_gid = '')))))
  308. ",
  309. intval($remote_user),
  310. dbesc($gs),
  311. intval($remote_user),
  312. dbesc($gs)
  313. );
  314. }
  315. }
  316. return $sql;
  317. }
  318. /*
  319. * Functions used to protect against Cross-Site Request Forgery
  320. * The security token has to base on at least one value that an attacker can't know - here it's the session ID and the private key.
  321. * In this implementation, a security token is reusable (if the user submits a form, goes back and resubmits the form, maybe with small changes;
  322. * or if the security token is used for ajax-calls that happen several times), but only valid for a certain amout of time (3hours).
  323. * The "typename" seperates the security tokens of different types of forms. This could be relevant in the following case:
  324. * A security token is used to protekt a link from CSRF (e.g. the "delete this profile"-link).
  325. * If the new page contains by any chance external elements, then the used security token is exposed by the referrer.
  326. * Actually, important actions should not be triggered by Links / GET-Requests at all, but somethimes they still are,
  327. * so this mechanism brings in some damage control (the attacker would be able to forge a request to a form of this type, but not to forms of other types).
  328. */
  329. function get_form_security_token($typename = '') {
  330. $a = get_app();
  331. $timestamp = time();
  332. $sec_hash = hash('whirlpool', $a->user['guid'] . $a->user['prvkey'] . session_id() . $timestamp . $typename);
  333. return $timestamp . '.' . $sec_hash;
  334. }
  335. function check_form_security_token($typename = '', $formname = 'form_security_token') {
  336. if (!x($_REQUEST, $formname)) return false;
  337. $hash = $_REQUEST[$formname];
  338. $max_livetime = 10800; // 3 hours
  339. $a = get_app();
  340. $x = explode('.', $hash);
  341. if (time() > (IntVal($x[0]) + $max_livetime)) return false;
  342. $sec_hash = hash('whirlpool', $a->user['guid'] . $a->user['prvkey'] . session_id() . $x[0] . $typename);
  343. return ($sec_hash == $x[1]);
  344. }
  345. function check_form_security_std_err_msg() {
  346. return t('The form security token was not correct. This probably happened because the form has been opened for too long (>3 hours) before submitting it.') . EOL;
  347. }
  348. function check_form_security_token_redirectOnErr($err_redirect, $typename = '', $formname = 'form_security_token') {
  349. if (!check_form_security_token($typename, $formname)) {
  350. $a = get_app();
  351. logger('check_form_security_token failed: user ' . $a->user['guid'] . ' - form element ' . $typename);
  352. logger('check_form_security_token failed: _REQUEST data: ' . print_r($_REQUEST, true), LOGGER_DATA);
  353. notice( check_form_security_std_err_msg() );
  354. goaway(App::get_baseurl() . $err_redirect );
  355. }
  356. }
  357. function check_form_security_token_ForbiddenOnErr($typename = '', $formname = 'form_security_token') {
  358. if (!check_form_security_token($typename, $formname)) {
  359. $a = get_app();
  360. logger('check_form_security_token failed: user ' . $a->user['guid'] . ' - form element ' . $typename);
  361. logger('check_form_security_token failed: _REQUEST data: ' . print_r($_REQUEST, true), LOGGER_DATA);
  362. header('HTTP/1.1 403 Forbidden');
  363. killme();
  364. }
  365. }
  366. // Returns an array of group id's this contact is a member of.
  367. // This array will only contain group id's related to the uid of this
  368. // DFRN contact. They are *not* neccessarily unique across the entire site.
  369. if(! function_exists('init_groups_visitor')) {
  370. function init_groups_visitor($contact_id) {
  371. $groups = array();
  372. $r = q("SELECT `gid` FROM `group_member`
  373. WHERE `contact-id` = %d ",
  374. intval($contact_id)
  375. );
  376. if (dbm::is_result($r)) {
  377. foreach($r as $rr)
  378. $groups[] = $rr['gid'];
  379. }
  380. return $groups;
  381. }}