]> git.mxchange.org Git - friendica.git/blob - src/Model/User.php
Merge branch 'develop' into task/3954-move-auth-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                 if ($using_invites) {
202                         if (!$invite_id) {
203                                 throw new Exception(t('An invitation is required.'));
204                         }
205
206                         if (!dba::exists('register', ['hash' => $invite_id])) {
207                                 throw new Exception(t('Invitation could not be verified.'));
208                         }
209                 }
210
211                 if (!x($username) || !x($email) || !x($nickname)) {
212                         if ($openid_url) {
213                                 if (!validate_url($openid_url)) {
214                                         throw new Exception(t('Invalid OpenID url'));
215                                 }
216                                 $_SESSION['register'] = 1;
217                                 $_SESSION['openid'] = $openid_url;
218
219                                 $openid = new \LightOpenID;
220                                 $openid->identity = $openid_url;
221                                 $openid->returnUrl = System::baseUrl() . '/openid';
222                                 $openid->required = array('namePerson/friendly', 'contact/email', 'namePerson');
223                                 $openid->optional = array('namePerson/first', 'media/image/aspect11', 'media/image/default');
224                                 try {
225                                         $authurl = $openid->authUrl();
226                                 } catch (Exception $e) {
227                                         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);
228                                 }
229                                 goaway($authurl);
230                                 // NOTREACHED
231                         }
232
233                         throw new Exception(t('Please enter the required information.'));
234                 }
235
236                 if (!validate_url($openid_url)) {
237                         $openid_url = '';
238                 }
239
240                 $err = '';
241
242                 // collapse multiple spaces in name
243                 $username = preg_replace('/ +/', ' ', $username);
244
245                 if (mb_strlen($username) > 48) {
246                         throw new Exception(t('Please use a shorter name.'));
247                 }
248                 if (mb_strlen($username) < 3) {
249                         throw new Exception(t('Name too short.'));
250                 }
251
252                 // So now we are just looking for a space in the full name.
253                 $loose_reg = Config::get('system', 'no_regfullname');
254                 if (!$loose_reg) {
255                         $username = mb_convert_case($username, MB_CASE_TITLE, 'UTF-8');
256                         if (!strpos($username, ' ')) {
257                                 throw new Exception(t("That doesn't appear to be your full \x28First Last\x29 name."));
258                         }
259                 }
260
261                 if (!allowed_email($email)) {
262                         throw new Exception(t('Your email domain is not among those allowed on this site.'));
263                 }
264
265                 if (!valid_email($email) || !validate_email($email)) {
266                         throw new Exception(t('Not a valid email address.'));
267                 }
268
269                 if (dba::exists('user', ['email' => $email])) {
270                         throw new Exception(t('Cannot use that email.'));
271                 }
272
273                 // Disallow somebody creating an account using openid that uses the admin email address,
274                 // since openid bypasses email verification. We'll allow it if there is not yet an admin account.
275                 if (x($a->config, 'admin_email') && strlen($openid_url)) {
276                         $adminlist = explode(',', str_replace(' ', '', strtolower($a->config['admin_email'])));
277                         if (in_array(strtolower($email), $adminlist)) {
278                                 throw new Exception(t('Cannot use that email.'));
279                         }
280                 }
281
282                 $nickname = $data['nickname'] = strtolower($nickname);
283
284                 if (!preg_match('/^[a-z0-9][a-z0-9\_]*$/', $nickname)) {
285                         throw new Exception(t('Your "nickname" can only contain "a-z", "0-9" and "_".'));
286                 }
287
288                 // Check existing and deleted accounts for this nickname.
289                 if (dba::exists('user', ['nickname' => $nickname])
290                         || dba::exists('userd', ['username' => $nickname])
291                 ) {
292                         throw new Exception(t('Nickname is already registered. Please choose another.'));
293                 }
294
295                 $new_password = strlen($password) ? $password : autoname(6) . mt_rand(100, 9999);
296                 $new_password_encoded = hash('whirlpool', $new_password);
297
298                 $return['password'] = $new_password;
299
300                 $keys = Crypto::newKeypair(4096);
301                 if ($keys === false) {
302                         throw new Exception(t('SERIOUS ERROR: Generation of security keys failed.'));
303                 }
304
305                 $prvkey = $keys['prvkey'];
306                 $pubkey = $keys['pubkey'];
307
308                 // Create another keypair for signing/verifying salmon protocol messages.
309                 $sres = Crypto::newKeypair(512);
310                 $sprvkey = $sres['prvkey'];
311                 $spubkey = $sres['pubkey'];
312
313                 $insert_result = dba::insert('user', [
314                         'guid'     => generate_user_guid(),
315                         'username' => $username,
316                         'password' => $new_password_encoded,
317                         'email'    => $email,
318                         'openid'   => $openid_url,
319                         'nickname' => $nickname,
320                         'pubkey'   => $pubkey,
321                         'prvkey'   => $prvkey,
322                         'spubkey'  => $spubkey,
323                         'sprvkey'  => $sprvkey,
324                         'verified' => $verified,
325                         'blocked'  => $blocked,
326                         'timezone' => 'UTC',
327                         'register_date' => datetime_convert(),
328                         'default-location' => ''
329                 ]);
330
331                 if ($insert_result) {
332                         $uid = dba::lastInsertId();
333                         $user = dba::select('user', [], ['uid' => $uid], ['limit' => 1]);
334                 } else {
335                         throw new Exception(t('An error occurred during registration. Please try again.'));
336                 }
337
338                 if (!$uid) {
339                         throw new Exception(t('An error occurred during registration. Please try again.'));
340                 }
341
342                 // if somebody clicked submit twice very quickly, they could end up with two accounts
343                 // due to race condition. Remove this one.
344                 $user_count = dba::count('user', ['nickname' => $nickname]);
345                 if ($user_count > 1) {
346                         dba::delete('user', ['uid' => $uid]);
347
348                         throw new Exception(t('Nickname is already registered. Please choose another.'));
349                 }
350
351                 $insert_result = dba::insert('profile', [
352                         'uid' => $uid,
353                         'name' => $username,
354                         'photo' => System::baseUrl() . "/photo/profile/{$uid}.jpg",
355                         'thumb' => System::baseUrl() . "/photo/avatar/{$uid}.jpg",
356                         'publish' => $publish,
357                         'is-default' => 1,
358                         'net-publish' => $netpublish,
359                         'profile-name' => t('default')
360                 ]);
361                 if (!$insert_result) {
362                         dba::delete('user', ['uid' => $uid]);
363
364                         throw new Exception(t('An error occurred creating your default profile. Please try again.'));
365                 }
366
367                 // Create the self contact
368                 if (!Contact::createSelfFromUserId($uid)) {
369                         dba::delete('user', ['uid' => $uid]);
370
371                         throw new Exception(t('An error occurred creating your self contact. Please try again.'));
372                 }
373
374                 // Create a group with no members. This allows somebody to use it
375                 // right away as a default group for new contacts.
376                 $def_gid = Group::create($uid, t('Friends'));
377                 if (!$def_gid) {
378                         dba::delete('user', ['uid' => $uid]);
379
380                         throw new Exception(t('An error occurred creating your default contact group. Please try again.'));
381                 }
382
383                 $fields = ['def_gid' => $def_gid];
384                 if (Config::get('system', 'newuser_private') && $def_gid) {
385                         $fields['allow_gid'] = '<' . $def_gid . '>';
386                 }
387
388                 dba::update('user', $fields, ['uid' => $uid]);
389
390                 // if we have no OpenID photo try to look up an avatar
391                 if (!strlen($photo)) {
392                         $photo = avatar_img($email);
393                 }
394
395                 // unless there is no avatar-plugin loaded
396                 if (strlen($photo)) {
397                         $photo_failure = false;
398
399                         $filename = basename($photo);
400                         $img_str = fetch_url($photo, true);
401                         // guess mimetype from headers or filename
402                         $type = Image::guessType($photo, true);
403
404                         $Image = new Image($img_str, $type);
405                         if ($Image->isValid()) {
406                                 $Image->scaleToSquare(175);
407
408                                 $hash = photo_new_resource();
409
410                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, t('Profile Photos'), 4);
411
412                                 if ($r === false) {
413                                         $photo_failure = true;
414                                 }
415
416                                 $Image->scaleDown(80);
417
418                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, t('Profile Photos'), 5);
419
420                                 if ($r === false) {
421                                         $photo_failure = true;
422                                 }
423
424                                 $Image->scaleDown(48);
425
426                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, t('Profile Photos'), 6);
427
428                                 if ($r === false) {
429                                         $photo_failure = true;
430                                 }
431
432                                 if (!$photo_failure) {
433                                         dba::update('photo', ['profile' => 1], ['resource-id' => $hash]);
434                                 }
435                         }
436                 }
437
438                 call_hooks('register_account', $uid);
439
440                 $return['user'] = $user;
441                 return $return;
442         }
443
444         /**
445          * @brief Sends pending registration confiƕmation email
446          *
447          * @param string $email
448          * @param string $sitename
449          * @param string $username
450          * @return NULL|boolean from notification() and email() inherited
451          */
452         public static function sendRegisterPendingEmail($email, $sitename, $username)
453         {
454                 $body = deindent(t('
455                         Dear %1$s,
456                                 Thank you for registering at %2$s. Your account is pending for approval by the administrator.
457                 '));
458
459                 $body = sprintf($body, $username, $sitename);
460
461                 return notification(array(
462                         'type' => SYSTEM_EMAIL,
463                         'to_email' => $email,
464                         'subject'=> sprintf( t('Registration at %s'), $sitename),
465                         'body' => $body));
466         }
467
468         /**
469          * @brief Sends registration confirmation
470          *
471          * It's here as a function because the mail is sent from different parts
472          *
473          * @param string $email
474          * @param string $sitename
475          * @param string $siteurl
476          * @param string $username
477          * @param string $password
478          * @return NULL|boolean from notification() and email() inherited
479          */
480         public static function sendRegisterOpenEmail($email, $sitename, $siteurl, $username, $password)
481         {
482                 $preamble = deindent(t('
483                         Dear %1$s,
484                                 Thank you for registering at %2$s. Your account has been created.
485                 '));
486                 $body = deindent(t('
487                         The login details are as follows:
488                                 Site Location:  %3$s
489                                 Login Name:     %1$s
490                                 Password:       %5$s
491
492                         You may change your password from your account "Settings" page after logging
493                         in.
494
495                         Please take a few moments to review the other account settings on that page.
496
497                         You may also wish to add some basic information to your default profile
498                         (on the "Profiles" page) so that other people can easily find you.
499
500                         We recommend setting your full name, adding a profile photo,
501                         adding some profile "keywords" (very useful in making new friends) - and
502                         perhaps what country you live in; if you do not wish to be more specific
503                         than that.
504
505                         We fully respect your right to privacy, and none of these items are necessary.
506                         If you are new and do not know anybody here, they may help
507                         you to make some new and interesting friends.
508
509
510                         Thank you and welcome to %2$s.'));
511
512                 $preamble = sprintf($preamble, $username, $sitename);
513                 $body = sprintf($body, $email, $sitename, $siteurl, $username, $password);
514
515                 return notification(array(
516                         'type' => SYSTEM_EMAIL,
517                         'to_email' => $email,
518                         'subject'=> sprintf( t('Registration details for %s'), $sitename),
519                         'preamble'=> $preamble,
520                         'body' => $body));
521         }
522
523         /**
524          * @param object $uid user to remove
525          * @return void
526          */
527         public static function remove($uid)
528         {
529                 if (!$uid) {
530                         return;
531                 }
532
533                 logger('Removing user: ' . $uid);
534
535                 $user = dba::select('user', [], ['uid' => $uid], ['limit' => 1]);
536
537                 call_hooks('remove_user', $user);
538
539                 // save username (actually the nickname as it is guaranteed
540                 // unique), so it cannot be re-registered in the future.
541                 dba::insert('userd', ['username' => $user['nickname']]);
542
543                 // The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php)
544                 dba::update('user', ['account_removed' => true, 'account_expires_on' => datetime_convert()], ['uid' => $uid]);
545                 Worker::add(PRIORITY_HIGH, "Notifier", "removeme", $uid);
546
547                 // Send an update to the directory
548                 Worker::add(PRIORITY_LOW, "Directory", $user['url']);
549
550                 if ($uid == local_user()) {
551                         unset($_SESSION['authenticated']);
552                         unset($_SESSION['uid']);
553                         goaway(System::baseUrl());
554                 }
555         }
556 }