[core] Make User::by_name / User::by_id not-null

Nearly everywhere that these functions are called, the result is assumed to be not-null, and a null will break things
This commit is contained in:
Shish 2024-08-31 20:35:13 +01:00 committed by Shish
parent 8b20fa3bc2
commit 845c8b3d85
20 changed files with 88 additions and 136 deletions

View file

@ -38,12 +38,8 @@ class CliApp extends \Symfony\Component\Console\Application
if ($input->hasParameterOption(['--user', '-u'])) { if ($input->hasParameterOption(['--user', '-u'])) {
$name = $input->getParameterOption(['--user', '-u']); $name = $input->getParameterOption(['--user', '-u']);
$user = User::by_name($name); $user = User::by_name($name);
if (is_null($user)) {
die("Unknown user '$name'\n");
} else {
send_event(new UserLoginEvent($user)); send_event(new UserLoginEvent($user));
} }
}
$log_level = SCORE_LOG_WARNING; $log_level = SCORE_LOG_WARNING;
if (true === $input->hasParameterOption(['--quiet', '-q'], true)) { if (true === $input->hasParameterOption(['--quiet', '-q'], true)) {

View file

@ -265,7 +265,6 @@ class Image implements \ArrayAccess
public function get_owner(): User public function get_owner(): User
{ {
$user = User::by_id($this->owner_id); $user = User::by_id($this->owner_id);
assert(!is_null($user));
return $user; return $user;
} }

View file

@ -628,7 +628,9 @@ function validate_input(array $inputs): array
if (in_array('user_id', $flags)) { if (in_array('user_id', $flags)) {
$id = int_escape($value); $id = int_escape($value);
if (in_array('exists', $flags)) { if (in_array('exists', $flags)) {
if (is_null(User::by_id($id))) { try {
User::by_id($id);
} catch (UserNotFound $e) {
throw new InvalidInput("User #$id does not exist"); throw new InvalidInput("User #$id does not exist");
} }
} }

View file

@ -241,18 +241,18 @@ if (class_exists("\\PHPUnit\\Framework\\TestCase")) {
// user things // user things
protected static function log_in_as_admin(): void protected static function log_in_as_admin(): void
{ {
send_event(new UserLoginEvent(User::by_name_ex(self::$admin_name))); send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
} }
protected static function log_in_as_user(): void protected static function log_in_as_user(): void
{ {
send_event(new UserLoginEvent(User::by_name_ex(self::$user_name))); send_event(new UserLoginEvent(User::by_name(self::$user_name)));
} }
protected static function log_out(): void protected static function log_out(): void
{ {
global $config; global $config;
send_event(new UserLoginEvent(User::by_id_ex($config->get_int("anon_id", 0)))); send_event(new UserLoginEvent(User::by_id($config->get_int("anon_id", 0))));
} }
// post things // post things

View file

@ -85,8 +85,9 @@ class User
global $cache, $config, $database; global $cache, $config, $database;
$user = $cache->get("user-session-obj:$name-$session"); $user = $cache->get("user-session-obj:$name-$session");
if (is_null($user)) { if (is_null($user)) {
try {
$user_by_name = User::by_name($name); $user_by_name = User::by_name($name);
if (is_null($user_by_name)) { } catch (UserNotFound $e) {
return null; return null;
} }
if ($user_by_name->get_session_id() === $session) { if ($user_by_name->get_session_id() === $session) {
@ -97,7 +98,7 @@ class User
return $user; return $user;
} }
public static function by_id(int $id): ?User public static function by_id(int $id): User
{ {
global $cache, $database; global $cache, $database;
if ($id === 1) { if ($id === 1) {
@ -110,57 +111,43 @@ class User
if ($id === 1) { if ($id === 1) {
$cache->set('user-id:'.$id, $row, 600); $cache->set('user-id:'.$id, $row, 600);
} }
return is_null($row) ? null : new User($row); if (is_null($row)) {
}
public static function by_id_ex(int $id): User
{
$u = User::by_id($id);
if (is_null($u)) {
throw new UserNotFound("Can't find any user with ID $id"); throw new UserNotFound("Can't find any user with ID $id");
} else {
return $u;
} }
return new User($row);
} }
#[Query(name: "user")] #[Query(name: "user")]
public static function by_name(string $name): ?User public static function by_name(string $name): User
{ {
global $database; global $database;
$row = $database->get_row("SELECT * FROM users WHERE LOWER(name) = LOWER(:name)", ["name" => $name]); $row = $database->get_row("SELECT * FROM users WHERE LOWER(name) = LOWER(:name)", ["name" => $name]);
return is_null($row) ? null : new User($row); if (is_null($row)) {
}
public static function by_name_ex(string $name): User
{
$u = User::by_name($name);
if (is_null($u)) {
throw new UserNotFound("Can't find any user named $name"); throw new UserNotFound("Can't find any user named $name");
} else { } else {
return $u; return new User($row);
} }
} }
public static function name_to_id(string $name): int public static function name_to_id(string $name): int
{ {
$u = User::by_name($name); return User::by_name($name)->id;
if (is_null($u)) {
throw new UserNotFound("Can't find any user named $name");
} else {
return $u->id;
}
} }
public static function by_name_and_pass(string $name, string $pass): ?User public static function by_name_and_pass(string $name, string $pass): User
{ {
try {
$my_user = User::by_name($name); $my_user = User::by_name($name);
} catch (UserNotFound $e) {
// If user tried to log in as "foo bar" and failed, try "foo_bar" // If user tried to log in as "foo bar" and failed, try "foo_bar"
if (!$my_user && str_contains($name, " ")) { try {
$my_user = User::by_name(str_replace(" ", "_", $name)); $my_user = User::by_name(str_replace(" ", "_", $name));
} catch (UserNotFound $e) {
log_warning("core-user", "Failed to log in as $name (Invalid username)");
throw $e;
}
} }
if ($my_user) {
if ($my_user->passhash == md5(strtolower($name) . $pass)) { if ($my_user->passhash == md5(strtolower($name) . $pass)) {
log_info("core-user", "Migrating from md5 to bcrypt for $name"); log_info("core-user", "Migrating from md5 to bcrypt for $name");
$my_user->set_password($pass); $my_user->set_password($pass);
@ -171,11 +158,8 @@ class User
return $my_user; return $my_user;
} else { } else {
log_warning("core-user", "Failed to log in as $name (Invalid password)"); log_warning("core-user", "Failed to log in as $name (Invalid password)");
throw new UserNotFound("Can't find anybody with that username and password");
} }
} else {
log_warning("core-user", "Failed to log in as $name (Invalid username)");
}
return null;
} }
@ -203,8 +187,11 @@ class User
public function set_name(string $name): void public function set_name(string $name): void
{ {
global $database; global $database;
if (User::by_name($name)) { try {
User::by_name($name);
throw new InvalidInput("Desired username is already in use"); throw new InvalidInput("Desired username is already in use");
} catch (UserNotFound $e) {
// if user is not found, we're good
} }
$old_name = $this->name; $old_name = $this->name;
$this->name = $name; $this->name = $name;

View file

@ -17,7 +17,7 @@ class UserClass
public static array $known_classes = []; public static array $known_classes = [];
#[Field] #[Field]
public ?string $name = null; public string $name;
public ?UserClass $parent = null; public ?UserClass $parent = null;
/** @var array<string, bool> */ /** @var array<string, bool> */

View file

@ -747,7 +747,6 @@ function _get_user(): User
if (is_null($my_user)) { if (is_null($my_user)) {
$my_user = User::by_id($config->get_int("anon_id", 0)); $my_user = User::by_id($config->get_int("anon_id", 0));
} }
assert(!is_null($my_user));
return $my_user; return $my_user;
} }

View file

@ -175,6 +175,8 @@ class AliasEditor extends Extension
foreach (explode("\n", $csv) as $line) { foreach (explode("\n", $csv) as $line) {
$parts = str_getcsv($line); $parts = str_getcsv($line);
if (count($parts) == 2) { if (count($parts) == 2) {
assert(is_string($parts[0]));
assert(is_string($parts[1]));
send_event(new AddAliasEvent($parts[0], $parts[1])); send_event(new AddAliasEvent($parts[0], $parts[1]));
$i++; $i++;
} }

View file

@ -187,6 +187,8 @@ class AutoTagger extends Extension
foreach (explode("\n", $csv) as $line) { foreach (explode("\n", $csv) as $line) {
$parts = str_getcsv($line); $parts = str_getcsv($line);
if (count($parts) == 2) { if (count($parts) == 2) {
assert(is_string($parts[0]));
assert(is_string($parts[1]));
send_event(new AddAutoTagEvent($parts[0], $parts[1])); send_event(new AddAutoTagEvent($parts[0], $parts[1]));
$i++; $i++;
} }

View file

@ -80,14 +80,12 @@ class DanbooruApi extends Extension
if (isset($_REQUEST['login']) && isset($_REQUEST['password'])) { if (isset($_REQUEST['login']) && isset($_REQUEST['password'])) {
// Get this user from the db, if it fails the user becomes anonymous // Get this user from the db, if it fails the user becomes anonymous
// Code borrowed from /ext/user // Code borrowed from /ext/user
try {
$name = $_REQUEST['login']; $name = $_REQUEST['login'];
$pass = $_REQUEST['password']; $pass = $_REQUEST['password'];
$duser = User::by_name_and_pass($name, $pass); $user = User::by_name_and_pass($name, $pass);
if (!is_null($duser)) { } catch (UserNotFound $e) {
$user = $duser;
} else {
$user = User::by_id($config->get_int("anon_id", 0)); $user = User::by_id($config->get_int("anon_id", 0));
assert(!is_null($user));
} }
send_event(new UserLoginEvent($user)); send_event(new UserLoginEvent($user));
} }

View file

@ -327,22 +327,12 @@ class NumericScore extends Extension
$event->add_querylet(new Querylet("numeric_score $cmp $score")); $event->add_querylet(new Querylet("numeric_score $cmp $score"));
} elseif (preg_match("/^upvoted_by[=|:](.*)$/i", $event->term, $matches)) { } elseif (preg_match("/^upvoted_by[=|:](.*)$/i", $event->term, $matches)) {
$duser = User::by_name($matches[1]); $duser = User::by_name($matches[1]);
if (is_null($duser)) {
throw new SearchTermParseException(
"Can't find the user named ".html_escape($matches[1])
);
}
$event->add_querylet(new Querylet( $event->add_querylet(new Querylet(
"images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=1)", "images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=1)",
["ns_user_id" => $duser->id] ["ns_user_id" => $duser->id]
)); ));
} elseif (preg_match("/^downvoted_by[=|:](.*)$/i", $event->term, $matches)) { } elseif (preg_match("/^downvoted_by[=|:](.*)$/i", $event->term, $matches)) {
$duser = User::by_name($matches[1]); $duser = User::by_name($matches[1]);
if (is_null($duser)) {
throw new SearchTermParseException(
"Can't find the user named ".html_escape($matches[1])
);
}
$event->add_querylet(new Querylet( $event->add_querylet(new Querylet(
"images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=-1)", "images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=:ns_user_id AND score=-1)",
["ns_user_id" => $duser->id] ["ns_user_id" => $duser->id]

View file

@ -50,10 +50,10 @@ class NumericScoreTest extends ShimmiePHPUnitTestCase
$this->assertEquals(404, $page->code); $this->assertEquals(404, $page->code);
# test errors # test errors
$this->assertException(SearchTermParseException::class, function () { $this->assertException(UserNotFound::class, function () {
$this->get_page("post/list/upvoted_by=asdfasdf/1"); $this->get_page("post/list/upvoted_by=asdfasdf/1");
}); });
$this->assertException(SearchTermParseException::class, function () { $this->assertException(UserNotFound::class, function () {
$this->get_page("post/list/downvoted_by=asdfasdf/1"); $this->get_page("post/list/downvoted_by=asdfasdf/1");
}); });

View file

@ -28,11 +28,7 @@ class PostOwner extends Extension
$owner = $event->get_param('owner'); $owner = $event->get_param('owner');
if ($user->can(Permissions::EDIT_IMAGE_OWNER) && !is_null($owner)) { if ($user->can(Permissions::EDIT_IMAGE_OWNER) && !is_null($owner)) {
$owner_ob = User::by_name($owner); $owner_ob = User::by_name($owner);
if (!is_null($owner_ob)) {
send_event(new OwnerSetEvent($event->image, $owner_ob)); send_event(new OwnerSetEvent($event->image, $owner_ob));
} else {
throw new UserNotFound("Error: No user with that name was found.");
}
} }
} }

View file

@ -67,12 +67,10 @@ class ReplaceFile extends Extension
$image->remove_image_only(); // Actually delete the old image file from disk $image->remove_image_only(); // Actually delete the old image file from disk
$target = warehouse_path(Image::IMAGE_DIR, $event->new_hash); $target = warehouse_path(Image::IMAGE_DIR, $event->new_hash);
if (!@copy($event->tmp_filename, $target)) { try {
$errors = error_get_last(); \Safe\copy($event->tmp_filename, $target);
throw new ImageReplaceException( } catch (\Exception $e) {
"Failed to copy file from uploads ({$event->tmp_filename}) to archive ($target): ". throw new ImageReplaceException("Failed to copy file from uploads ({$event->tmp_filename}) to archive ($target): {$e->getMessage()}");
"{$errors['type']} / {$errors['message']}"
);
} }
unlink($event->tmp_filename); unlink($event->tmp_filename);

View file

@ -239,7 +239,7 @@ class SetupBlock extends Block
public function add_shorthand_int_option(string $name, string $label = null, bool $table_row = false): void public function add_shorthand_int_option(string $name, string $label = null, bool $table_row = false): void
{ {
$val = to_shorthand_int($this->config->get_int($name)); $val = to_shorthand_int($this->config->get_int($name, 0));
$html = "<input type='text' id='$name' name='_config_$name' value='$val' size='6'>\n"; $html = "<input type='text' id='$name' name='_config_$name' value='$val' size='6'>\n";
$html .= "<input type='hidden' name='_type_$name' value='int'>\n"; $html .= "<input type='hidden' name='_type_$name' value='int'>\n";

View file

@ -267,14 +267,9 @@ class SourceHistory extends Extension
if (!is_null($name)) { if (!is_null($name)) {
$duser = User::by_name($name); $duser = User::by_name($name);
if (is_null($duser)) {
$this->theme->add_status($name, "user not found");
return;
} else {
$select_code[] = 'user_id = :user_id'; $select_code[] = 'user_id = :user_id';
$select_args['user_id'] = $duser->id; $select_args['user_id'] = $duser->id;
} }
}
if (!is_null($ip)) { if (!is_null($ip)) {
$select_code[] = 'user_ip = :user_ip'; $select_code[] = 'user_ip = :user_ip';

View file

@ -313,14 +313,9 @@ class TagHistory extends Extension
if (!is_null($name)) { if (!is_null($name)) {
$duser = User::by_name($name); $duser = User::by_name($name);
if (is_null($duser)) {
$this->theme->add_status($name, "user not found");
return;
} else {
$select_code[] = 'user_id = :user_id'; $select_code[] = 'user_id = :user_id';
$select_args['user_id'] = $duser->id; $select_args['user_id'] = $duser->id;
} }
}
if (!is_null($ip)) { if (!is_null($ip)) {
$select_code[] = 'user_ip = :user_ip'; $select_code[] = 'user_ip = :user_ip';

View file

@ -90,17 +90,16 @@ class LoginResult
public static function login(string $username, string $password): LoginResult public static function login(string $username, string $password): LoginResult
{ {
global $config; global $config;
try {
$duser = User::by_name_and_pass($username, $password); $duser = User::by_name_and_pass($username, $password);
if (!is_null($duser)) {
return new LoginResult( return new LoginResult(
$duser, $duser,
$duser->get_session_id(), $duser->get_session_id(),
null null
); );
} else { } catch (UserNotFound $ex) {
$anon = User::by_id($config->get_int("anon_id", 0));
return new LoginResult( return new LoginResult(
$anon, User::by_id($config->get_int("anon_id", 0)),
null, null,
"No user found" "No user found"
); );
@ -310,11 +309,14 @@ class UserPage extends Extension
} }
if ($event->page_matches("user/{name}")) { if ($event->page_matches("user/{name}")) {
try {
$display_user = User::by_name($event->get_arg('name')); $display_user = User::by_name($event->get_arg('name'));
if (!is_null($display_user) && ($display_user->id != $config->get_int("anon_id"))) { if ($display_user->id == $config->get_int("anon_id")) {
throw new UserNotFound("No such user");
}
$e = send_event(new UserPageBuildingEvent($display_user)); $e = send_event(new UserPageBuildingEvent($display_user));
$this->display_stats($e); $this->display_stats($e);
} else { } catch (UserNotFound $ex) {
$this->theme->display_error( $this->theme->display_error(
404, 404,
"No Such User", "No Such User",
@ -519,8 +521,11 @@ class UserPage extends Extension
"letters, numbers, dash, and underscore" "letters, numbers, dash, and underscore"
); );
} }
if (User::by_name($name)) { try {
User::by_name($name);
throw new UserCreationException("That username is already taken"); throw new UserCreationException("That username is already taken");
} catch (UserNotFound $ex) {
// user not found is good
} }
if (!captcha_check()) { if (!captcha_check()) {
throw new UserCreationException("Error in captcha"); throw new UserCreationException("Error in captcha");
@ -588,11 +593,6 @@ class UserPage extends Extension
$matches = []; $matches = [];
if (preg_match(self::USER_SEARCH_REGEX, $event->term, $matches)) { if (preg_match(self::USER_SEARCH_REGEX, $event->term, $matches)) {
$duser = User::by_name($matches[2]); $duser = User::by_name($matches[2]);
if (is_null($duser)) {
throw new SearchTermParseException(
"Can't find the user named " . html_escape($matches[2])
);
}
$event->add_querylet(new Querylet("images.owner_id {$matches[1]}= {$duser->id}")); $event->add_querylet(new Querylet("images.owner_id {$matches[1]}= {$duser->id}"));
} elseif (preg_match(self::USER_ID_SEARCH_REGEX, $event->term, $matches)) { } elseif (preg_match(self::USER_ID_SEARCH_REGEX, $event->term, $matches)) {
$user_id = int_escape($matches[2]); $user_id = int_escape($matches[2]);
@ -630,7 +630,6 @@ class UserPage extends Extension
global $config, $page; global $config, $page;
$duser = User::by_name_and_pass($name, $pass); $duser = User::by_name_and_pass($name, $pass);
if (!is_null($duser)) {
send_event(new UserLoginEvent($duser)); send_event(new UserLoginEvent($duser));
$duser->set_login_cookie(); $duser->set_login_cookie();
$page->set_mode(PageMode::REDIRECT); $page->set_mode(PageMode::REDIRECT);
@ -641,9 +640,6 @@ class UserPage extends Extension
} else { } else {
$page->set_redirect(make_link("user")); $page->set_redirect(make_link("user"));
} }
} else {
$this->theme->display_error(401, "Error", "No user with those details was found");
}
} }
private function page_logout(): void private function page_logout(): void
@ -669,9 +665,7 @@ class UserPage extends Extension
private function page_recover(string $username): void private function page_recover(string $username): void
{ {
$my_user = User::by_name($username); $my_user = User::by_name($username);
if (is_null($my_user)) { if (is_null($my_user->email)) {
$this->theme->display_error(404, "Error", "There's no user with that name");
} elseif (is_null($my_user->email)) {
$this->theme->display_error(400, "Error", "That user has no registered email address"); $this->theme->display_error(400, "Error", "That user has no registered email address");
} else { } else {
throw new ServerError("Email sending not implemented"); throw new ServerError("Email sending not implemented");

View file

@ -66,7 +66,7 @@ class UserPageTest extends ShimmiePHPUnitTestCase
'email' => '', 'email' => '',
]); ]);
}); });
$this->assertNull(User::by_name('testnew'), "Anon can't create others"); $this->assertException(UserNotFound::class, function () {User::by_name('testnew');});
$this->log_in_as_admin(); $this->log_in_as_admin();
$this->post_page('user_admin/create_other', [ $this->post_page('user_admin/create_other', [
@ -76,6 +76,6 @@ class UserPageTest extends ShimmiePHPUnitTestCase
'email' => '', 'email' => '',
]); ]);
$this->assertEquals(302, $page->code); $this->assertEquals(302, $page->code);
$this->assertNotNull(User::by_name('testnew'), "Admin can create others"); $this->assertNotNull(User::by_name('testnew'));
} }
} }

View file

@ -66,7 +66,6 @@ class UserConfig extends Extension
global $database; global $database;
$user = User::by_id($id); $user = User::by_id($id);
$user_config = new DatabaseConfig($database, "user_config", "user_id", "$id"); $user_config = new DatabaseConfig($database, "user_config", "user_id", "$id");
send_event(new InitUserConfigEvent($user, $user_config)); send_event(new InitUserConfigEvent($user, $user_config));
return $user_config; return $user_config;