Skip to content

general hardening fixes, duplication prevention#4

Open
multiman155 wants to merge 1 commit intoMCCitiesNetwork:mainfrom
multiman155:main
Open

general hardening fixes, duplication prevention#4
multiman155 wants to merge 1 commit intoMCCitiesNetwork:mainfrom
multiman155:main

Conversation

@multiman155
Copy link
Copy Markdown

This PR makes the plugin safer and harder to break by mistake.

The biggest fix is for rent and extend. Before this change, the code could take money from one player and give it to another player before it knew for sure that the database update worked. If the database step failed, that could create extra money by accident. Now the code waits until the database change succeeds before finishing the payment, and it refunds correctly if something goes wrong.

This PR also moves more permission checks into the main shared logic of the plugin. Before, some checks only happened in the command code, which meant there were ways those protections could be missed. Now the important rules about who is allowed to change ownership, landlord, tenant, prices, and agent data are enforced in the core logic itself.

It also makes sign-triggered commands safer. Sign commands are now cleaned and checked before they run, so blank, broken, multi-line, or malformed commands are skipped instead of being executed.

On top of that, this fixes the database test setup after the leasehold table rename, adds more tests for security-sensitive cases, and adds GitHub security automation with dependency review, CodeQL, and Dependabot.

Verification: ./gradlew.bat test

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea here but i think this can still go wrong

if rentRegion succeeds and the placeholder lookup fails after that this still comes back as null and refunds the player even though the lease already
changed

feels like the write result needs to be handled separate from the placeholder lookup so we do not refund after a successful commit

looks like ExtendCommand has the same pattern too

Copy link
Copy Markdown
Collaborator

@md5sha256 md5sha256 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot to unpack in this PR, there is too much going on. Please break this PR up into multiple PRs.

  • Do not bundle the CI/workflow changes with logic changes
  • Split the sign command sanitizing logic into another PR (this is a welcome change, just too muddled up in here)
  • Fixes to the unit test I will fix separately (since I can't fix by merging the PR right now as-is), so please revert those
  • In general, a preference was made to have worldguard always be the authoritative source of truth when it comes to permission checks, I've commented on the places I think changes were made incorrectly/do not reflect that preference to be fixed accordingly.
  • The economy changes should be reasonable, notably its hard to understand because theres so much going on.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its generally unnecessary to add checks for valid region logic here. The job of this command is to manage agent invites and it should stay in that scope. Furthermore, we may extend the scope of agents to rentals in the future anyway.

case RealtyLogicImpl.RemoveSanctionedAuctioneerResult.NoFreeholdContract() ->
sender.sendMessage(messages.messageFor(MessageKeys.AGENT_INVITE_NO_FREEHOLD,
Placeholder.unparsed("region", regionId)));
case RealtyLogicImpl.RemoveSanctionedAuctioneerResult.NotTitleHolder() ->
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point here is the world guard is the authoritative source of "who is the owner". So if there is somehow a desync, worldguard will prevail.


private void executeCancel(@NotNull CommandContext<CommandSourceStack> ctx) {
CommandSender sender = ctx.sender().getSender();
if (!(sender instanceof Player player)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This command should be executable by console.

sender.sendMessage(messages.messageFor(MessageKeys.AUCTION_NOT_SANCTIONED,
Placeholder.unparsed("region", regionId)));
case RealtyLogicImpl.CancelAuctionResult.NoFreeholdContract ignored ->
sender.sendMessage(messages.messageFor(MessageKeys.AUCTION_NO_FREEHOLD_CONTRACT,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again same as before, i think this command should not be validating whether it is a freehold contract or not, it's job is to handle auctions and that should be the end the scope of the checks.

case RealtyLogicImpl.RenewLeaseholdResult.NoLeaseholdContract ignored ->
sender.sendMessage(messages.messageFor(MessageKeys.EXTEND_NO_LEASEHOLD_CONTRACT,
Placeholder.unparsed("region", regionId)));
case RealtyLogicImpl.RenewLeaseholdResult.NotTenant ignored ->
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worldguard is the authoritative source of truth

}
String regionId = region.region().getId();
UUID worldId = region.world().getUID();
if (sender instanceof Player player
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worldguard is authoritative, so these checks did not need to be moved into the database layer.

}
String regionId = region.region().getId();
UUID worldId = region.world().getUID();
if (sender instanceof Player player
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worldguard is authoritative, so these checks did not need to be moved into the database layer.

}
String regionId = region.region().getId();
UUID worldId = region.world().getUID();
if (sender instanceof Player player
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worldguard is authoritative, so these checks did not need to be moved into the database layer.

sender.sendMessage(messages.messageFor(MessageKeys.UNRENT_NO_LEASEHOLD_CONTRACT,
Placeholder.unparsed("region", regionId)));
}
case RealtyLogicImpl.UnrentResult.NotTenant ignored -> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worldguard is authoritative

}
String regionId = region.region().getId();
UUID worldId = region.world().getUID();
if (sender instanceof Player player
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worldguard is authoritative, so these checks did not need to be moved into the database layer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants