2020-07-22 10:47:56 +09:00
< ? php
// {{{ License
2020-09-06 05:19:58 +09:00
2020-07-22 10:47:56 +09:00
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
2020-09-06 05:19:58 +09:00
2020-07-22 10:47:56 +09:00
// }}}
namespace App\Entity ;
2020-07-22 20:45:03 +09:00
use App\Core\DB\DB ;
2020-08-13 08:37:59 +09:00
use App\Core\Entity ;
2020-07-22 10:47:56 +09:00
use App\Core\UserRoles ;
2020-07-22 20:45:03 +09:00
use App\Util\Common ;
2021-07-29 06:30:09 +09:00
use App\Util\Exception\DuplicateFoundException ;
2020-07-22 10:47:56 +09:00
use DateTimeInterface ;
2020-07-26 01:09:43 +09:00
use Exception ;
2020-07-27 12:36:34 +09:00
use libphonenumber\PhoneNumber ;
2020-07-22 10:47:56 +09:00
use Symfony\Component\Security\Core\User\UserInterface ;
/**
* Entity for users
*
* @ category DB
* @ package GNUsocial
*
* @ author Zach Copley < zach @ status . net >
* @ copyright 2010 StatusNet Inc .
* @ author Mikael Nordfeldth < mmn @ hethane . se >
* @ copyright 2009 - 2014 Free Software Foundation , Inc http :// www . fsf . org
2021-02-20 08:29:43 +09:00
* @ author Hugo Sales < hugo @ hsal . es >
* @ copyright 2020 - 2021 Free Software Foundation , Inc http :// www . fsf . org
2020-07-22 10:47:56 +09:00
* @ license https :// www . gnu . org / licenses / agpl . html GNU AGPL v3 or later
*/
2020-08-13 08:37:59 +09:00
class LocalUser extends Entity implements UserInterface
2020-07-22 10:47:56 +09:00
{
// {{{ Autocode
2021-05-06 01:03:03 +09:00
// @codeCoverageIgnoreStart
2020-09-06 05:19:58 +09:00
private int $id ;
private string $nickname ;
2020-07-22 10:47:56 +09:00
private ? string $password ;
private ? string $outgoing_email ;
private ? string $incoming_email ;
2020-07-25 11:00:33 +09:00
private ? bool $is_email_verified ;
2020-07-22 10:47:56 +09:00
private ? string $language ;
private ? string $timezone ;
2020-07-27 12:36:34 +09:00
private ? PhoneNumber $phone_number ;
2020-07-22 10:47:56 +09:00
private ? int $sms_carrier ;
private ? string $sms_email ;
private ? string $uri ;
private ? bool $auto_follow_back ;
private ? int $follow_policy ;
private ? bool $is_stream_private ;
2021-04-28 06:24:48 +09:00
private \DateTimeInterface $created ;
private \DateTimeInterface $modified ;
2020-07-22 10:47:56 +09:00
2020-09-06 05:19:58 +09:00
public function setId ( int $id ) : self
{
$this -> id = $id ;
return $this ;
}
public function getId () : int
{
return $this -> id ;
}
public function setNickname ( string $nickname ) : self
2020-07-22 10:47:56 +09:00
{
$this -> nickname = $nickname ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-09-06 05:19:58 +09:00
public function getNickname () : string
2020-07-22 10:47:56 +09:00
{
return $this -> nickname ;
}
public function setPassword ( ? string $password ) : self
{
$this -> password = $password ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-22 10:47:56 +09:00
public function getPassword () : ? string
{
return $this -> password ;
}
public function setOutgoingEmail ( ? string $outgoing_email ) : self
{
$this -> outgoing_email = $outgoing_email ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-22 10:47:56 +09:00
public function getOutgoingEmail () : ? string
{
return $this -> outgoing_email ;
}
public function setIncomingEmail ( ? string $incoming_email ) : self
{
$this -> incoming_email = $incoming_email ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-22 10:47:56 +09:00
public function getIncomingEmail () : ? string
{
return $this -> incoming_email ;
}
2020-07-25 11:00:33 +09:00
public function setIsEmailVerified ( ? bool $is_email_verified ) : self
{
$this -> is_email_verified = $is_email_verified ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-25 11:00:33 +09:00
public function getIsEmailVerified () : ? bool
{
return $this -> is_email_verified ;
}
2020-07-22 10:47:56 +09:00
public function setLanguage ( ? string $language ) : self
{
$this -> language = $language ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-22 10:47:56 +09:00
public function getLanguage () : ? string
{
return $this -> language ;
}
public function setTimezone ( ? string $timezone ) : self
{
$this -> timezone = $timezone ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-22 10:47:56 +09:00
public function getTimezone () : ? string
{
return $this -> timezone ;
}
2020-07-27 12:36:34 +09:00
public function setPhoneNumber ( ? PhoneNumber $phone_number ) : self
2020-07-22 10:47:56 +09:00
{
2020-07-26 09:18:15 +09:00
$this -> phone_number = $phone_number ;
2020-07-22 10:47:56 +09:00
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-27 12:36:34 +09:00
public function getPhoneNumber () : ? PhoneNumber
2020-07-22 10:47:56 +09:00
{
2020-07-26 09:18:15 +09:00
return $this -> phone_number ;
2020-07-22 10:47:56 +09:00
}
public function setSmsCarrier ( ? int $sms_carrier ) : self
{
$this -> sms_carrier = $sms_carrier ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-22 10:47:56 +09:00
public function getSmsCarrier () : ? int
{
return $this -> sms_carrier ;
}
public function setSmsEmail ( ? string $sms_email ) : self
{
$this -> sms_email = $sms_email ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-22 10:47:56 +09:00
public function getSmsEmail () : ? string
{
return $this -> sms_email ;
}
public function setUri ( ? string $uri ) : self
{
$this -> uri = $uri ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-22 10:47:56 +09:00
public function getUri () : ? string
{
return $this -> uri ;
}
public function setAutoFollowBack ( ? bool $auto_follow_back ) : self
{
$this -> auto_follow_back = $auto_follow_back ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-22 10:47:56 +09:00
public function getAutoFollowBack () : ? bool
{
return $this -> auto_follow_back ;
}
public function setFollowPolicy ( ? int $follow_policy ) : self
{
$this -> follow_policy = $follow_policy ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-22 10:47:56 +09:00
public function getFollowPolicy () : ? int
{
return $this -> follow_policy ;
}
public function setIsStreamPrivate ( ? bool $is_stream_private ) : self
{
$this -> is_stream_private = $is_stream_private ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2020-07-22 10:47:56 +09:00
public function getIsStreamPrivate () : ? bool
{
return $this -> is_stream_private ;
}
2021-05-06 01:03:03 +09:00
public function setCreated ( DateTimeInterface $created ) : self
2020-07-22 10:47:56 +09:00
{
$this -> created = $created ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2021-05-06 01:03:03 +09:00
public function getCreated () : DateTimeInterface
2020-07-22 10:47:56 +09:00
{
return $this -> created ;
}
2021-05-06 01:03:03 +09:00
public function setModified ( DateTimeInterface $modified ) : self
2020-07-22 10:47:56 +09:00
{
$this -> modified = $modified ;
return $this ;
}
2020-08-09 01:11:18 +09:00
2021-05-06 01:03:03 +09:00
public function getModified () : DateTimeInterface
2020-07-22 10:47:56 +09:00
{
return $this -> modified ;
}
2021-05-06 01:03:03 +09:00
// @codeCoverageIgnoreEnd
2020-07-22 10:47:56 +09:00
// }}} Autocode
2020-08-13 08:57:22 +09:00
public function getActor ()
2020-07-22 10:47:56 +09:00
{
2020-09-06 05:19:58 +09:00
return DB :: find ( 'gsactor' , [ 'id' => $this -> id ]);
2020-07-22 10:47:56 +09:00
}
/**
* Returns the roles granted to the user
*/
public function getRoles ()
{
2020-09-11 05:42:54 +09:00
return UserRoles :: toArray ( $this -> getActor () -> getRoles ());
2020-07-22 10:47:56 +09:00
}
/**
* Returns the password used to authenticate the user .
*
2020-07-25 11:00:33 +09:00
* Implemented in the auto code
*/
2020-07-22 10:47:56 +09:00
/**
* Returns the salt that was originally used to encode the password .
* BCrypt and Argon2 generate their own salts
*/
public function getSalt ()
{
return null ;
}
/**
* Returns the username used to authenticate the user .
*/
public function getUsername ()
{
return $this -> nickname ;
}
/**
* Removes sensitive data from the user .
*
* This is important if , at any given point , sensitive information like
* the plain - text password is stored on this object .
*/
public function eraseCredentials ()
{
}
2020-07-22 20:45:03 +09:00
2021-07-29 06:30:09 +09:00
/**
* Is the nickname or email already in use locally ?
*
* @ return self Returns self if nickname or email found
*/
public static function findByNicknameOrEmail ( string $nickname , string $email ) : ? self
{
$users = DB :: findBy ( 'local_user' , [ 'or' => [ 'nickname' => $nickname , 'outgoing_email' => $email , 'incoming_email' => $email ]]);
switch ( count ( $users )) {
case 0 :
return null ;
case 1 :
return $users [ 0 ];
default :
2021-08-19 01:30:02 +09:00
// @codeCoverageIgnoreStart
2021-07-29 06:30:09 +09:00
throw new DuplicateFoundException ( 'Multiple values in table local_user match the requested criteria' );
2021-08-19 01:30:02 +09:00
// @codeCoverageIgnoreEnd
2021-07-29 06:30:09 +09:00
}
}
2021-08-05 05:06:58 +09:00
/**
* When authenticating , check a user ' s password in a timing safe
* way . Will update the password by rehashing if deemed necessary
*/
public function checkPassword ( string $password_plain_text ) : bool
2020-07-22 20:45:03 +09:00
{
2020-07-26 01:09:43 +09:00
// Timing safe password verification
2021-08-05 05:06:58 +09:00
if ( password_verify ( $password_plain_text , $this -> password )) {
2020-07-26 01:09:43 +09:00
// Update old formats
if ( password_needs_rehash ( $this -> password ,
2020-07-27 12:36:34 +09:00
self :: algoNameToConstant ( Common :: config ( 'security' , 'algorithm' )),
Common :: config ( 'security' , 'options' ))
2020-07-26 01:09:43 +09:00
) {
2021-08-19 01:30:02 +09:00
// @codeCoverageIgnoreStart
2021-08-05 05:06:58 +09:00
$this -> changePassword ( null , $password_plain_text , override : true );
2021-08-19 01:30:02 +09:00
// @codeCoverageIgnoreEnd
2020-07-26 01:09:43 +09:00
}
2020-07-22 20:45:03 +09:00
return true ;
}
return false ;
}
2021-08-05 05:06:58 +09:00
public function changePassword ( ? string $old_password_plain_text , string $new_password_plain_text , bool $override = false ) : bool
2020-07-22 20:45:03 +09:00
{
2021-08-05 05:06:58 +09:00
if ( $override || $this -> checkPassword ( $old_password_plain_text )) {
$this -> setPassword ( self :: hashPassword ( $new_password_plain_text ));
2020-07-22 20:45:03 +09:00
DB :: flush ();
2021-08-05 05:06:58 +09:00
return true ;
2020-07-22 20:45:03 +09:00
}
2021-08-05 05:06:58 +09:00
return false ;
2020-07-22 20:45:03 +09:00
}
2020-08-19 23:00:57 +09:00
public static function hashPassword ( string $password )
2020-07-22 20:45:03 +09:00
{
2020-07-26 01:09:43 +09:00
$algorithm = self :: algoNameToConstant ( Common :: config ( 'security' , 'algorithm' ));
$options = Common :: config ( 'security' , 'options' );
return password_hash ( $password , $algorithm , $options );
}
2021-08-19 01:30:02 +09:00
/**
* Public for testing
*/
public static function algoNameToConstant ( string $algo )
2020-07-26 01:09:43 +09:00
{
switch ( $algo ) {
2020-07-22 20:45:03 +09:00
case 'bcrypt' :
case 'argon2i' :
2020-07-26 01:09:43 +09:00
case 'argon2d' :
2020-07-22 20:45:03 +09:00
case 'argon2id' :
2020-07-28 07:18:23 +09:00
$c = 'PASSWORD_' . strtoupper ( $algo );
if ( defined ( $c )) {
return constant ( $c );
}
// fallthrough
// no break
2020-07-26 01:09:43 +09:00
default :
throw new Exception ( 'Unsupported or unsafe hashing algorithm requested' );
2020-07-22 20:45:03 +09:00
}
}
2020-08-13 08:37:59 +09:00
public static function schemaDef () : array
{
return [
'name' => 'local_user' ,
'description' => 'local users, bots, etc' ,
'fields' => [
2021-03-12 07:16:17 +09:00
'id' => [ 'type' => 'int' , 'foreign key' => true , 'target' => 'GSActor.id' , 'multiplicity' => 'one to one' , 'not null' => true , 'description' => 'foreign key to gsactor table' ],
2021-04-11 20:03:32 +09:00
'nickname' => [ 'type' => 'varchar' , 'not null' => true , 'length' => 64 , 'description' => 'nickname or username, foreign key to gsactor' ],
2021-02-23 06:34:59 +09:00
'password' => [ 'type' => 'varchar' , 'length' => 191 , 'description' => 'salted password, can be null for users with federated authentication' ],
'outgoing_email' => [ 'type' => 'varchar' , 'length' => 191 , 'description' => 'email address for password recovery, notifications, etc.' ],
'incoming_email' => [ 'type' => 'varchar' , 'length' => 191 , 'description' => 'email address for post-by-email' ],
'is_email_verified' => [ 'type' => 'bool' , 'default' => false , 'description' => 'Whether the user opened the comfirmation email' ],
'language' => [ 'type' => 'varchar' , 'length' => 50 , 'description' => 'preferred language' ],
'timezone' => [ 'type' => 'varchar' , 'length' => 50 , 'description' => 'timezone' ],
2020-08-13 08:37:59 +09:00
'phone_number' => [ 'type' => 'phone_number' , 'description' => 'phone number' ],
2021-03-12 07:16:17 +09:00
'sms_carrier' => [ 'type' => 'int' , 'foreign key' => true , 'target' => 'SmsCarrier.id' , 'multiplicity' => 'one to one' , 'description' => 'foreign key to sms_carrier' ],
2021-02-23 06:34:59 +09:00
'sms_email' => [ 'type' => 'varchar' , 'length' => 191 , 'description' => 'built from sms and carrier (see sms_carrier)' ],
'uri' => [ 'type' => 'varchar' , 'length' => 191 , 'description' => 'universally unique identifier, usually a tag URI' ],
'auto_follow_back' => [ 'type' => 'bool' , 'default' => false , 'description' => 'automatically follow users who follow us' ],
'follow_policy' => [ 'type' => 'int' , 'size' => 'tiny' , 'default' => 0 , 'description' => '0 = anybody can follow; 1 = require approval' ],
'is_stream_private' => [ 'type' => 'bool' , 'default' => false , 'description' => 'whether to limit all notices to followers only' ],
'created' => [ 'type' => 'datetime' , 'not null' => true , 'default' => 'CURRENT_TIMESTAMP' , 'description' => 'date this record was created' ],
'modified' => [ 'type' => 'timestamp' , 'not null' => true , 'default' => 'CURRENT_TIMESTAMP' , 'description' => 'date this record was modified' ],
2020-08-13 08:37:59 +09:00
],
2020-09-06 05:19:58 +09:00
'primary key' => [ 'id' ],
2020-08-13 08:37:59 +09:00
'unique keys' => [
2020-09-06 05:19:58 +09:00
'user_nickname_key' => [ 'nickname' ],
2020-08-13 08:37:59 +09:00
'user_outgoing_email_key' => [ 'outgoing_email' ],
'user_incoming_email_key' => [ 'incoming_email' ],
'user_phone_number_key' => [ 'phone_number' ],
'user_uri_key' => [ 'uri' ],
],
'indexes' => [
'user_nickname_idx' => [ 'nickname' ],
'user_created_idx' => [ 'created' ],
'user_sms_email_idx' => [ 'sms_email' ],
],
];
}
2020-07-22 10:47:56 +09:00
}