]> git.mxchange.org Git - friendica.git/blob - src/Model/User.php
Crypto to src
[friendica.git] / src / Model / User.php
1 <?php
2
3 /**
4  * @file src/Model/User.php
5  * @brief This file includes the User class with user related database functions
6  */
7
8 namespace Friendica\Model;
9
10 use Friendica\Core\Config;
11 use Friendica\Core\PConfig;
12 use Friendica\Core\System;
13 use Friendica\Core\Worker;
14 use Friendica\Database\DBM;
15 use Friendica\Model\Contact;
16 use Friendica\Model\Group;
17 use Friendica\Model\Photo;
18 use Friendica\Object\Image;
19 use Friendica\Util\Crypto;
20 use dba;
21 use Exception;
22
23 require_once 'boot.php';
24 require_once 'include/dba.php';
25 require_once 'include/enotify.php';
26 require_once 'include/network.php';
27 require_once 'library/openid.php';
28 require_once 'include/pgettext.php';
29 require_once 'include/plugin.php';
30 require_once 'include/text.php';
31 /**
32  * @brief This class handles User related functions
33  */
34 class User
35 {
36         /**
37          * @brief Get owner data by user id
38          *
39          * @param int $uid
40          * @return boolean|array
41          */
42         public static function getOwnerDataById($uid) {
43                 $r = dba::fetch_first("SELECT
44                         `contact`.*,
45                         `user`.`prvkey` AS `uprvkey`,
46                         `user`.`timezone`,
47                         `user`.`nickname`,
48                         `user`.`sprvkey`,
49                         `user`.`spubkey`,
50                         `user`.`page-flags`,
51                         `user`.`account-type`,
52                         `user`.`prvnets`
53                         FROM `contact`
54                         INNER JOIN `user`
55                                 ON `user`.`uid` = `contact`.`uid`
56                         WHERE `contact`.`uid` = ?
57                         AND `contact`.`self`
58                         LIMIT 1",
59                         $uid
60                 );
61                 if (!DBM::is_result($r)) {
62                         return false;
63                 }
64                 return $r;
65         }
66
67         /**
68          * @brief Returns the default group for a given user and network
69          *
70          * @param int $uid User id
71          * @param string $network network name
72          *
73          * @return int group id
74          */
75         public static function getDefaultGroup($uid, $network = '')
76         {
77                 $default_group = 0;
78
79                 if ($network == NETWORK_OSTATUS) {
80                         $default_group = PConfig::get($uid, "ostatus", "default_group");
81                 }
82
83                 if ($default_group != 0) {
84                         return $default_group;
85                 }
86
87                 $user = dba::select('user', ['def_gid'], ['uid' => $uid], ['limit' => 1]);
88
89                 if (DBM::is_result($user)) {
90                         $default_group = $user["def_gid"];
91                 }
92
93                 return $default_group;
94         }
95
96
97         /**
98          * @brief Authenticate a user with a clear text password
99          *
100          * User info can be any of the following:
101          * - User DB object
102          * - User Id
103          * - User email or username or nickname
104          * - User array with at least the uid and the hashed password
105          *
106          * @param mixed $user_info
107          * @param string $password
108          * @return boolean
109          */
110         public static function authenticate($user_info, $password)
111         {
112                 if (is_object($user_info)) {
113                         $user = (array) $user_info;
114                 } elseif (is_int($user_info)) {
115                         $user = dba::select('user',
116                                 ['uid', 'password'],
117                                 [
118                                         'uid' => $user_info,
119                                         'blocked' => 0,
120                                         'account_expired' => 0,
121                                         'account_removed' => 0,
122                                         'verified' => 1
123                                 ],
124                                 ['limit' => 1]
125                         );
126                 } elseif (is_string($user_info)) {
127                         $user = dba::fetch_first('SELECT `uid`, `password`
128                                 FROM `user`
129                                 WHERE (`email` = ? OR `username` = ? OR `nickname` = ?)
130                                 AND `blocked` = 0
131                                 AND `account_expired` = 0
132                                 AND `account_removed` = 0
133                                 AND `verified` = 1
134                                 LIMIT 1',
135                                 $user_info,
136                                 $user_info,
137                                 $user_info
138                         );
139                 } else {
140                         $user = $user_info;
141                 }
142
143                 if (!DBM::is_result($user) || !isset($user['uid']) || !isset($user['password'])) {
144                         return false;
145                 }
146
147                 $password_hashed = hash('whirlpool', $password);
148
149                 if ($password_hashed !== $user['password']) {
150                         return false;
151                 }
152
153                 return $user['uid'];
154         }
155
156         /**
157          * @brief Catch-all user creation function
158          *
159          * Creates a user from the provided data array, either form fields or OpenID.
160          * Required: { username, nickname, email } or { openid_url }
161          *
162          * Performs the following:
163          * - Sends to the OpenId auth URL (if relevant)
164          * - Creates new key pairs for crypto
165          * - Create self-contact
166          * - Create profile image
167          *
168          * @param array $data
169          * @return string
170          * @throw Exception
171          */
172         public static function create(array $data)
173         {
174                 $a = get_app();
175                 $return = ['user' => null, 'password' => ''];
176
177                 $using_invites = Config::get('system', 'invitation_only');
178                 $num_invites   = Config::get('system', 'number_invites');
179
180                 $invite_id  = x($data, 'invite_id')  ? notags(trim($data['invite_id']))  : '';
181                 $username   = x($data, 'username')   ? notags(trim($data['username']))   : '';
182                 $nickname   = x($data, 'nickname')   ? notags(trim($data['nickname']))   : '';
183                 $email      = x($data, 'email')      ? notags(trim($data['email']))      : '';
184                 $openid_url = x($data, 'openid_url') ? notags(trim($data['openid_url'])) : '';
185                 $photo      = x($data, 'photo')      ? notags(trim($data['photo']))      : '';
186                 $password   = x($data, 'password')   ? trim($data['password'])           : '';
187                 $password1  = x($data, 'password1')  ? trim($data['password1'])          : '';
188                 $confirm    = x($data, 'confirm')    ? trim($data['confirm'])            : '';
189                 $blocked    = x($data, 'blocked')    ? intval($data['blocked'])          : 0;
190                 $verified   = x($data, 'verified')   ? intval($data['verified'])         : 0;
191
192                 $publish = x($data, 'profile_publish_reg') && intval($data['profile_publish_reg']) ? 1 : 0;
193                 $netpublish = strlen(Config::get('system', 'directory')) ? $publish : 0;
194
195                 if ($password1 != $confirm) {
196                         throw new Exception(t('Passwords do not match. Password unchanged.'));
197                 } elseif ($password1 != '') {
198                         $password = $password1;
199                 }
200
201                 $tmp_str = $openid_url;
202
203                 if ($using_invites) {
204                         if (!$invite_id) {
205                                 throw new Exception(t('An invitation is required.'));
206                         }
207
208                         if (!dba::exists('register', ['hash' => $invite_id])) {
209                                 throw new Exception(t('Invitation could not be verified.'));
210                         }
211                 }
212
213                 if (!x($username) || !x($email) || !x($nickname)) {
214                         if ($openid_url) {
215                                 if (!validate_url($tmp_str)) {
216                                         throw new Exception(t('Invalid OpenID url'));
217                                 }
218                                 $_SESSION['register'] = 1;
219                                 $_SESSION['openid'] = $openid_url;
220
221                                 $openid = new \LightOpenID;
222                                 $openid->identity = $openid_url;
223                                 $openid->returnUrl = System::baseUrl() . '/openid';
224                                 $openid->required = array('namePerson/friendly', 'contact/email', 'namePerson');
225                                 $openid->optional = array('namePerson/first', 'media/image/aspect11', 'media/image/default');
226                                 try {
227                                         $authurl = $openid->authUrl();
228                                 } catch (Exception $e) {
229                                         throw new Exception(t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.') . EOL . EOL . t('The error message was:') . $e->getMessage(), 0, $e);
230                                 }
231                                 goaway($authurl);
232                                 // NOTREACHED
233                         }
234
235                         throw new Exception(t('Please enter the required information.'));
236                 }
237
238                 if (!validate_url($tmp_str)) {
239                         $openid_url = '';
240                 }
241
242                 $err = '';
243
244                 // collapse multiple spaces in name
245                 $username = preg_replace('/ +/', ' ', $username);
246
247                 if (mb_strlen($username) > 48) {
248                         throw new Exception(t('Please use a shorter name.'));
249                 }
250                 if (mb_strlen($username) < 3) {
251                         throw new Exception(t('Name too short.'));
252                 }
253
254                 // So now we are just looking for a space in the full name.
255                 $loose_reg = Config::get('system', 'no_regfullname');
256                 if (!$loose_reg) {
257                         $username = mb_convert_case($username, MB_CASE_TITLE, 'UTF-8');
258                         if (!strpos($username, ' ')) {
259                                 throw new Exception(t("That doesn't appear to be your full \x28First Last\x29 name."));
260                         }
261                 }
262
263                 if (!allowed_email($email)) {
264                         throw new Exception(t('Your email domain is not among those allowed on this site.'));
265                 }
266
267                 if (!valid_email($email) || !validate_email($email)) {
268                         throw new Exception(t('Not a valid email address.'));
269                 }
270
271                 if (dba::exists('user', ['email' => $email])) {
272                         throw new Exception(t('Cannot use that email.'));
273                 }
274
275                 // Disallow somebody creating an account using openid that uses the admin email address,
276                 // since openid bypasses email verification. We'll allow it if there is not yet an admin account.
277                 if (x($a->config, 'admin_email') && strlen($openid_url)) {
278                         $adminlist = explode(',', str_replace(' ', '', strtolower($a->config['admin_email'])));
279                         if (in_array(strtolower($email), $adminlist)) {
280                                 throw new Exception(t('Cannot use that email.'));
281                         }
282                 }
283
284                 $nickname = $data['nickname'] = strtolower($nickname);
285
286                 if (!preg_match('/^[a-z0-9][a-z0-9\_]*$/', $nickname)) {
287                         throw new Exception(t('Your "nickname" can only contain "a-z", "0-9" and "_".'));
288                 }
289
290                 // Check existing and deleted accounts for this nickname.
291                 if (dba::exists('user', ['nickname' => $nickname])
292                         || dba::exists('userd', ['username' => $nickname])
293                 ) {
294                         throw new Exception(t('Nickname is already registered. Please choose another.'));
295                 }
296
297                 $new_password = strlen($password) ? $password : autoname(6) . mt_rand(100, 9999);
298                 $new_password_encoded = hash('whirlpool', $new_password);
299
300                 $return['password'] = $new_password;
301
302                 $keys = Crypto::newKeypair(4096);
303                 if ($keys === false) {
304                         throw new Exception(t('SERIOUS ERROR: Generation of security keys failed.'));
305                 }
306
307                 $prvkey = $keys['prvkey'];
308                 $pubkey = $keys['pubkey'];
309
310                 // Create another keypair for signing/verifying salmon protocol messages.
311                 $sres = Crypto::newKeypair(512);
312                 $sprvkey = $sres['prvkey'];
313                 $spubkey = $sres['pubkey'];
314
315                 $insert_result = dba::insert('user', [
316                         'guid'     => generate_user_guid(),
317                         'username' => $username,
318                         'password' => $new_password_encoded,
319                         'email'    => $email,
320                         'openid'   => $openid_url,
321                         'nickname' => $nickname,
322                         'pubkey'   => $pubkey,
323                         'prvkey'   => $prvkey,
324                         'spubkey'  => $spubkey,
325                         'sprvkey'  => $sprvkey,
326                         'verified' => $verified,
327                         'blocked'  => $blocked,
328                         'timezone' => 'UTC',
329                         'register_date' => datetime_convert(),
330                         'default-location' => ''
331                 ]);
332
333                 if ($insert_result) {
334                         $uid = dba::lastInsertId();
335                         $user = dba::select('user', [], ['uid' => $uid], ['limit' => 1]);
336                 } else {
337                         throw new Exception(t('An error occurred during registration. Please try again.'));
338                 }
339
340                 if (!$uid) {
341                         throw new Exception(t('An error occurred during registration. Please try again.'));
342                 }
343
344                 // if somebody clicked submit twice very quickly, they could end up with two accounts
345                 // due to race condition. Remove this one.
346                 $user_count = dba::count('user', ['nickname' => $nickname]);
347                 if ($user_count > 1) {
348                         dba::delete('user', ['uid' => $uid]);
349
350                         throw new Exception(t('Nickname is already registered. Please choose another.'));
351                 }
352
353                 $insert_result = dba::insert('profile', [
354                         'uid' => $uid,
355                         'name' => $username,
356                         'photo' => System::baseUrl() . "/photo/profile/{$uid}.jpg",
357                         'thumb' => System::baseUrl() . "/photo/avatar/{$uid}.jpg",
358                         'publish' => $publish,
359                         'is-default' => 1,
360                         'net-publish' => $netpublish,
361                         'profile-name' => t('default')
362                 ]);
363                 if (!$insert_result) {
364                         dba::delete('user', ['uid' => $uid]);
365
366                         throw new Exception(t('An error occurred creating your default profile. Please try again.'));
367                 }
368
369                 // Create the self contact
370                 if (!Contact::createSelfFromUserId($uid)) {
371                         dba::delete('user', ['uid' => $uid]);
372
373                         throw new Exception(t('An error occurred creating your self contact. Please try again.'));
374                 }
375
376                 // Create a group with no members. This allows somebody to use it
377                 // right away as a default group for new contacts.
378                 $def_gid = Group::create($uid, t('Friends'));
379                 if (!$def_gid) {
380                         dba::delete('user', ['uid' => $uid]);
381
382                         throw new Exception(t('An error occurred creating your default contact group. Please try again.'));
383                 }
384
385                 $fields = ['def_gid' => $def_gid];
386                 if (Config::get('system', 'newuser_private') && $def_gid) {
387                         $fields['allow_gid'] = '<' . $def_gid . '>';
388                 }
389
390                 dba::update('user', $fields, ['uid' => $uid]);
391
392                 // if we have no OpenID photo try to look up an avatar
393                 if (!strlen($photo)) {
394                         $photo = avatar_img($email);
395                 }
396
397                 // unless there is no avatar-plugin loaded
398                 if (strlen($photo)) {
399                         $photo_failure = false;
400
401                         $filename = basename($photo);
402                         $img_str = fetch_url($photo, true);
403                         // guess mimetype from headers or filename
404                         $type = Image::guessType($photo, true);
405
406                         $Image = new Image($img_str, $type);
407                         if ($Image->isValid()) {
408                                 $Image->scaleToSquare(175);
409
410                                 $hash = photo_new_resource();
411
412                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, t('Profile Photos'), 4);
413
414                                 if ($r === false) {
415                                         $photo_failure = true;
416                                 }
417
418                                 $Image->scaleDown(80);
419
420                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, t('Profile Photos'), 5);
421
422                                 if ($r === false) {
423                                         $photo_failure = true;
424                                 }
425
426                                 $Image->scaleDown(48);
427
428                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, t('Profile Photos'), 6);
429
430                                 if ($r === false) {
431                                         $photo_failure = true;
432                                 }
433
434                                 if (!$photo_failure) {
435                                         dba::update('photo', ['profile' => 1], ['resource-id' => $hash]);
436                                 }
437                         }
438                 }
439
440                 call_hooks('register_account', $uid);
441
442                 $return['user'] = $user;
443                 return $return;
444         }
445
446         /**
447          * @brief Sends pending registration confiƕmation email
448          *
449          * @param string $email
450          * @param string $sitename
451          * @param string $username
452          * @return NULL|boolean from notification() and email() inherited
453          */
454         public static function sendRegisterPendingEmail($email, $sitename, $username)
455         {
456                 $body = deindent(t('
457                         Dear %1$s,
458                                 Thank you for registering at %2$s. Your account is pending for approval by the administrator.
459                 '));
460
461                 $body = sprintf($body, $username, $sitename);
462
463                 return notification(array(
464                         'type' => SYSTEM_EMAIL,
465                         'to_email' => $email,
466                         'subject'=> sprintf( t('Registration at %s'), $sitename),
467                         'body' => $body));
468         }
469
470         /**
471          * @brief Sends registration confirmation
472          *
473          * It's here as a function because the mail is sent from different parts
474          *
475          * @param string $email
476          * @param string $sitename
477          * @param string $siteurl
478          * @param string $username
479          * @param string $password
480          * @return NULL|boolean from notification() and email() inherited
481          */
482         public static function sendRegisterOpenEmail($email, $sitename, $siteurl, $username, $password)
483         {
484                 $preamble = deindent(t('
485                         Dear %1$s,
486                                 Thank you for registering at %2$s. Your account has been created.
487                 '));
488                 $body = deindent(t('
489                         The login details are as follows:
490                                 Site Location:  %3$s
491                                 Login Name:     %1$s
492                                 Password:       %5$s
493
494                         You may change your password from your account "Settings" page after logging
495                         in.
496
497                         Please take a few moments to review the other account settings on that page.
498
499                         You may also wish to add some basic information to your default profile
500                         (on the "Profiles" page) so that other people can easily find you.
501
502                         We recommend setting your full name, adding a profile photo,
503                         adding some profile "keywords" (very useful in making new friends) - and
504                         perhaps what country you live in; if you do not wish to be more specific
505                         than that.
506
507                         We fully respect your right to privacy, and none of these items are necessary.
508                         If you are new and do not know anybody here, they may help
509                         you to make some new and interesting friends.
510
511
512                         Thank you and welcome to %2$s.'));
513
514                 $preamble = sprintf($preamble, $username, $sitename);
515                 $body = sprintf($body, $email, $sitename, $siteurl, $username, $password);
516
517                 return notification(array(
518                         'type' => SYSTEM_EMAIL,
519                         'to_email' => $email,
520                         'subject'=> sprintf( t('Registration details for %s'), $sitename),
521                         'preamble'=> $preamble,
522                         'body' => $body));
523         }
524
525         /**
526          * @param object $uid user to remove
527          * @return void
528          */
529         public static function remove($uid)
530         {
531                 if (!$uid) {
532                         return;
533                 }
534
535                 logger('Removing user: ' . $uid);
536
537                 $user = dba::select('user', [], ['uid' => $uid], ['limit' => 1]);
538
539                 call_hooks('remove_user', $user);
540
541                 // save username (actually the nickname as it is guaranteed
542                 // unique), so it cannot be re-registered in the future.
543                 dba::insert('userd', ['username' => $user['nickname']]);
544
545                 // The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php)
546                 dba::update('user', ['account_removed' => true, 'account_expires_on' => datetime_convert()], ['uid' => $uid]);
547                 Worker::add(PRIORITY_HIGH, "Notifier", "removeme", $uid);
548
549                 // Send an update to the directory
550                 Worker::add(PRIORITY_LOW, "Directory", $user['url']);
551
552                 if ($uid == local_user()) {
553                         unset($_SESSION['authenticated']);
554                         unset($_SESSION['uid']);
555                         goaway(System::baseUrl());
556                 }
557         }
558 }