Merge pull request #1083 from AuthMe/master

5.3 [W.I.P.]
This commit is contained in:
Gabriele C 2017-03-26 19:52:12 +02:00 committed by GitHub
commit 1f02ef6f6e
372 changed files with 11746 additions and 4085 deletions

185
.checkstyle.xml Normal file
View File

@ -0,0 +1,185 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="warning"/>
<property name="fileExtensions" value="java, properties, xml"/>
<module name="SuppressWarningsFilter" />
<module name="TreeWalker">
<module name="SuppressWarningsHolder"/>
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format" value="\\u00(08|09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message" value="Avoid using corresponding octal or Unicode escape."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="LineLength">
<property name="max" value="120"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<module name="TodoComment">
<!-- Allow comments which have an issue number as they are accounted for in the issue tracker -->
<property name="format" value="TODO(?! #\d+:)|FIXME"/>
</module>
<module name="GenericWhitespace"/>
<module name="AvoidStarImport"/>
<module name="RedundantImport"/>
<module name="UnusedImports"/>
<module name="OneTopLevelClass"/>
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InnerTypeLast"/>
<module name="VisibilityModifier"/>
<module name="AvoidNestedBlocks"/>
<module name="NoLineWrap"/>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<module name="RightCurly">
<property name="option" value="alone"/>
<property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT"/>
</module>
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
<module name="MissingSwitchDefault"/>
<module name="DefaultComesLast"/>
<module name="FallThrough"/>
<module name="NestedTryDepth"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<property name="allowOneCharVarInForLoop" value="true"/>
<message key="name.invalidPattern"
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="NoFinalizer"/>
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="1"/>
</module>
<module name="DeclarationOrder"/>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="MethodParamPad"/>
<module name="StringLiteralEquality"/>
<module name="BooleanExpressionComplexity">
<property name="max" value="5"/>
</module>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR "/>
</module>
<module name="CyclomaticComplexity">
<property name="max" value="15"/>
</module>
<module name="JavaNCSS">
<property name="methodMaximum" value="40"/>
<property name="classMaximum" value="1000"/>
</module>
<module name="AnnotationLocation">
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="NonEmptyAtclauseDescription"/>
<module name="JavadocTagContinuationIndentation"/>
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="JavadocMethod">
<property name="scope" value="package"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="minLineCount" value="4"/>
<property name="allowedAnnotations" value="Override, Test, SectionComments, EventHandler"/>
<property name="tokens" value="METHOD_DEF, ANNOTATION_FIELD_DEF"/> <!-- exclude CTOR_DEF -->
</module>
<module name="JavadocMethod">
<property name="scope" value="private"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="minLineCount" value="16"/>
<property name="allowedAnnotations" value="Override, Test, SectionComments, EventHandler"/>
<property name="tokens" value="METHOD_DEF, ANNOTATION_FIELD_DEF"/> <!-- exclude CTOR_DEF -->
</module>
<!-- TODO Checkstyle/#4089: need "allowedAnnotations" property to skip @Comment fields
<module name="JavadocVariable">
<property name="scope" value="package"/>
<property name="tokens" value="VARIABLE_DEF"/>
</module>
-->
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="SingleLineJavadoc">
<property name="ignoredTags" value="@return"/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="ignore|ignored"/>
</module>
<module name="MissingOverride"/>
<module name="EqualsHashCode"/>
<module name="EqualsAvoidNull"/>
</module>
<module name="FileTabCharacter"/>
</module>

30
.codeclimate.yml Normal file
View File

@ -0,0 +1,30 @@
engines:
duplication:
enabled: true
config:
languages:
- java
- php
checkstyle:
enabled: true
channel: beta
config: '.checkstyle.xml'
pmd:
enabled: true
channel: beta
checks:
AvoidUsingHardCodedIP:
enabled: false
ratings:
paths:
# Check only production files
- 'src/main/java/**'
exclude_paths:
# Exclude code from third-party sources
- 'src/main/java/fr/xephi/authme/mail/OAuth2Provider.java'
- 'src/main/java/fr/xephi/authme/mail/OAuth2SaslClient.java'
- 'src/main/java/fr/xephi/authme/mail/OAuth2SaslClientFactory.java'
- 'src/main/java/fr/xephi/authme/security/crypts/BCryptService.java'
- 'src/main/java/fr/xephi/authme/security/crypts/PhpBB.java'
- 'src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java'
- 'src/main/java/fr/xephi/authme/security/crypts/Wordpress.java'

4
.gitignore vendored
View File

@ -1,5 +1,6 @@
### Java files ###
*.class
MANIFEST.MF
# Package Files
#*.jar
@ -9,7 +10,8 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# Mac OS
.DS_Store
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm

View File

@ -1,4 +1,8 @@
sudo: false
addons:
apt:
packages:
- oracle-java8-installer
language: java
jdk: oraclejdk8

View File

@ -2,7 +2,7 @@
<p align="center"><strong>The most used authentication plugin for Spigot and CraftBukkit!</strong></p>
<hr>
#####Links and Contacts:
##### Links and Contacts:
- GitHub pages:
- [Main](https://github.com/Xephi/AuthMeReloaded) (**release sources, issue tracker!**)
@ -19,41 +19,35 @@
- Project status:
- Dependencies: [![Dependencies status](https://www.versioneye.com/user/projects/57b182e8d6ffcd0032d7cf2d/badge.svg)](https://www.versioneye.com/user/projects/57b182e8d6ffcd0032d7cf2d)
- Test coverage: [![Coverage status](https://coveralls.io/repos/AuthMe-Team/AuthMeReloaded/badge.svg?branch=master&service=github)](https://coveralls.io/github/AuthMe-Team/AuthMeReloaded?branch=master)
- Code climate: [![Code Climate](https://codeclimate.com/github/AuthMe/AuthMeReloaded/badges/gpa.svg)](https://codeclimate.com/github/AuthMe/AuthMeReloaded)
- Development resources:
- <a href="http://ci.xephi.fr/job/AuthMeReloaded/javadoc/">JavaDocs</a>
- <a href="http://ci.xephi.fr/plugin/repository/everything/">Maven Repository</a>
#####Statistics:
McStats: http://mcstats.org/plugin/AuthMe
<img src="http://i.mcstats.org/AuthMe/Global+Statistics.borderless.png">
<img src="http://i.mcstats.org/AuthMe/Rank.borderless.png">
<img src="http://i.mcstats.org/AuthMe/Version+Demographics.borderless.png">
- Statistics:
- bStats: [AuthMe on bstats.org](https://bstats.org/plugin/bukkit/AuthMe)
<hr>
#####Compiling requirements:
##### Compiling requirements:
>- JDK 1.8
>- Maven
>- Git/Github (Optional)
#####How to compile the project:
##### How to compile the project:
>- Clone the project with Git/Github
>- Execute command "mvn clean package"
#####Running requirements:
##### Running requirements:
>- Java 1.8
>- TacoSpigot, PaperSpigot, Spigot or CraftBukkit (1.7.10, 1.8.X, 1.9.X, 1.10.X, 1.11.X)
>- ProtocolLib (optional, required by some features)
<hr>
###Plugin Description:
### Plugin Description:
#####"The best authentication plugin for the Bukkit/Spigot API!"
##### "The best authentication plugin for the Bukkit/Spigot API!"
Prevent username stealing on your server!<br>
Use it to secure your Offline mode server or to increase your Online mode server's protection!
@ -66,7 +60,7 @@ Each command and every feature can be enabled or disabled from our well structur
You can also create your own translation file and, if you want, you can share it with us! :)
####Features:
#### Features:
<ul>
<li><strong>E-Mail Recovery System !!!</strong></li>
<li>Username spoofing protection.</li>
@ -92,7 +86,7 @@ You can also create your own translation file and, if you want, you can share it
<li>DoubleSaltedMD5: SALTED2MD5</li>
<li>WordPress: WORDPRESS</li>
</ul></li>
<li>Custom MySQL tables/columns names (useful with forums databases)</li>
<li>Custom MySQL tables/columns names (useful with forum databases)</li>
<li><strong>Cached database queries!</strong></li>
<li><strong>Fully compatible with Citizens2, CombatTag, CombatTagPlus!</strong></li>
<li>Compatible with Minecraft mods like <strong>BuildCraft or RedstoneCraft</strong></li>
@ -105,18 +99,18 @@ You can also create your own translation file and, if you want, you can share it
<li><strong>Import your old database from other plugins like Rakamak, xAuth, CrazyLogin, RoyalAuth and vAuth!</strong></li>
</ul>
####Configuration
#### Configuration
<a href="https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/config.md">How to configure Authme</a>
####Email Recovery Dependency
#### Email Recovery Dependency
<a href="http://dev.bukkit.org/server-mods/authme-reloaded/pages/how-to-configure-email-recovery-system/">How to configure email recovery system?</a>
####Commands
#### Commands
[Command list and usage](https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/commands.md)
####Permissions
#### Permissions
- authme.player.* - for all user commands
- authme.admin.* - for all admin commands
- [List of all permission nodes](http://github.com/AuthMe-Team/AuthMeReloaded/blob/master/docs/permission_nodes.md)
####How To
#### How To
- [How to import database from xAuth](http://dev.bukkit.org/server-mods/authme-reloaded/pages/how-to-import-database-from-xauth/)
- [Website integration](http://dev.bukkit.org/server-mods/authme-reloaded/pages/web-site-integration/)
- [How to convert from Rakamak](http://dev.bukkit.org/server-mods/authme-reloaded/pages/how-to-import-database-from-rakamak/)
@ -124,14 +118,14 @@ You can also create your own translation file and, if you want, you can share it
<hr>
#####Sponsor
##### Sponsor
GameHosting.it is leader in Italy as Game Server Provider. With its own DataCenter offers Anti-DDoS solutions at affordable prices. Game Server of Minecraft based on Multicraft are equipped with the latest technology in hardware.
[![GameHosting](http://www.gamehosting.it/images/bn3.png)](http://www.gamehosting.it)
#####Credits
##### Credits
<p>Team members: look at the <a href="https://github.com/AuthMe/AuthMeReloaded/blob/master/team.txt">member list</a>
<p>Credit for old version of the plugin to: d4rkwarriors, fabe1337, Whoami2 and pomo4ka</p>
<p>Thanks also to: AS1LV3RN1NJA, Hoeze and eprimex</p>
#####GeoIP License
##### GeoIP License
This product uses data from the GeoLite API created by MaxMind, available at http://www.maxmind.com

View File

@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Sun Oct 23 18:25:12 CEST 2016. See docs/commands/commands.tpl.md -->
<!-- File auto-generated on Wed Mar 22 23:10:32 CET 2017. See docs/commands/commands.tpl.md -->
## AuthMe Commands
You can use the following commands to use the features of AuthMe. Mandatory arguments are marked with `< >`
@ -47,6 +47,8 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
<br />Requires `authme.admin.converter`
- **/authme messages**: Adds missing messages to the current messages file.
<br />Requires `authme.admin.updatemessages`
- **/authme debug** [child] [params]: Allows various operations for debugging.
<br />Requires `authme.debug`
- **/authme help** [query]: View detailed help for /authme commands.
- **/login** &lt;password>: Command to log in using AuthMeReloaded.
<br />Requires `authme.player.login`
@ -69,7 +71,11 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
<br />Requires `authme.player.email.add`
- **/email change** &lt;oldEmail> &lt;newEmail>: Change an email address of your account.
<br />Requires `authme.player.email.change`
- **/email recover** &lt;email> [code]: Recover your account using an Email address by sending a mail containing a new password.
- **/email recover** &lt;email>: Recover your account using an Email address by sending a mail containing a new password.
<br />Requires `authme.player.email.recover`
- **/email code** &lt;code>: Recover your account by submitting a code delivered to your email.
<br />Requires `authme.player.email.recover`
- **/email setpassword** &lt;password>: Set a new password after successfully recovering your account.
<br />Requires `authme.player.email.recover`
- **/email help** [query]: View detailed help for /email commands.
- **/captcha** &lt;captcha>: Captcha command for AuthMeReloaded.
@ -78,4 +84,4 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
---
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Oct 23 18:25:12 CEST 2016
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed Mar 22 23:10:32 CET 2017

View File

@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Sat Jan 14 22:12:16 CET 2017. See docs/config/config.tpl.md -->
<!-- File auto-generated on Wed Mar 22 23:10:33 CET 2017. See docs/config/config.tpl.md -->
## AuthMe Configuration
The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder,
@ -10,7 +10,7 @@ the generated config.yml file.
DataSource:
# What type of database do you want to use?
# Valid values: sqlite, mysql
# Valid values: SQLITE, MYSQL
backend: 'SQLITE'
# Enable database caching, should improve database performance
caching: true
@ -18,9 +18,11 @@ DataSource:
mySQLHost: '127.0.0.1'
# Database port
mySQLPort: '3306'
# Username about Database Connection Infos
# Connect to MySQL database over SSL
mySQLUseSSL: true
# Username to connect to the MySQL database
mySQLUsername: 'authme'
# Password about Database Connection Infos
# Password to connect to the MySQL database
mySQLPassword: '12345'
# Database Name, use with converters or as SQLITE database name
mySQLDatabase: 'authme'
@ -34,8 +36,6 @@ DataSource:
mySQLRealName: 'realname'
# Column for storing players passwords
mySQLColumnPassword: 'password'
# Request mysql over SSL
mySQLUseSSL: true
# Column for storing players emails
mySQLColumnEmail: 'email'
# Column for storing if a player is logged in or not
@ -71,19 +71,14 @@ ExternalBoardOptions:
phpbbTablePrefix: 'phpbb_'
# phpBB activated group ID; 2 is the default registered group defined by phpBB
phpbbActivatedGroupId: 2
# IP Board table prefix defined during the IP Board installation process
IPBTablePrefix: 'ipb_'
# IP Board default group ID; 3 is the default registered group defined by IP Board
IPBActivatedGroupId: 3
# XenForo default group ID; 2 is the default registered group defined by Xenforo
XFActivatedGroupId: 2
# Wordpress prefix defined during WordPress installation
wordpressTablePrefix: 'wp_'
Converter:
Rakamak:
# Rakamak file name
fileName: 'users.rak'
# Rakamak use IP?
useIP: false
# Rakamak IP file name
ipFileName: 'UsersIp.rak'
CrazyLogin:
# CrazyLogin database file name
fileName: 'accounts.db'
settings:
sessions:
# Do you want to enable the session feature?
@ -94,13 +89,8 @@ settings:
# expired, he will not need to authenticate.
enabled: false
# After how many minutes should a session expire?
# Remember that sessions will end only after the timeout, and
# if the player's IP has changed but the timeout hasn't expired,
# the player will be kicked from the server due to invalid session
# A player's session ends after the timeout or if his IP has changed
timeout: 10
# Should the session expire if the player tries to log in with
# another IP address?
sessionExpireOnIpChange: true
# Message language, available languages:
# https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md
messagesLanguage: 'en'
@ -161,6 +151,8 @@ settings:
# AllowedRestrictedUser:
# - playername;127.0.0.1
AllowedRestrictedUser: []
# Ban unknown IPs trying to log in with a restricted username?
banUnsafedIP: false
# Should unregistered players be kicked immediately?
kickNonRegistered: false
# Should players be kicked on wrong password?
@ -177,7 +169,7 @@ settings:
# After how many seconds should players who fail to login or register
# be kicked? Set to 0 to disable.
timeout: 30
# Regex syntax of allowed characters in the player name.
# Regex pattern of allowed characters in the player name.
allowedNicknameCharacters: '[a-zA-Z0-9_]*'
# How far can unregistered players walk?
# Set to 0 for unlimited radius
@ -189,8 +181,6 @@ settings:
# Should we display all other accounts from a player when he joins?
# permission: /authme.admin.accounts
displayOtherAccounts: true
# Ban ip when the ip is not the ip registered in database
banUnsafedIP: false
# Spawn priority; values: authme, essentials, multiverse, default
spawnPriority: 'authme,essentials,multiverse,default'
# Maximum Login authorized by IP
@ -223,18 +213,6 @@ settings:
minPasswordLength: 5
# Maximum length of password
passwordMaxLength: 30
# This is a very important option: every time a player joins the server,
# if they are registered, AuthMe will switch him to unLoggedInGroup.
# This should prevent all major exploits.
# You can set up your permission plugin with this special group to have no permissions,
# or only permission to chat (or permission to send private messages etc.).
# The better way is to set up this group with few permissions, so if a player
# tries to exploit an account they can do only what you've defined for the group.
# After, a logged in player will be moved to his correct permissions group!
# Please note that the group name is case-sensitive, so 'admin' is different from 'Admin'
# Otherwise your group will be wiped and the player will join in the default group []!
# Example unLoggedinGroup: NotLogged
unLoggedinGroup: 'unLoggedinGroup'
# Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL,
# MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB,
# PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (for developers only). See full list at
@ -317,17 +295,31 @@ settings:
# Do we need to prevent people to login with another case?
# If Xephi is registered, then Xephi can login, but not XEPHI/xephi/XePhI
preventOtherCase: true
permission:
# Take care with this option; if you want
# to use group switching of AuthMe
# for unloggedIn players, set this setting to true.
# Default is false.
EnablePermissionCheck: false
GroupOptions:
# Enables switching a player to defined permission groups before they log in.
# See below for a detailed explanation.
enablePermissionCheck: false
# This is a very important option: if a registered player joins the server
# AuthMe will switch him to unLoggedInGroup. This should prevent all major exploits.
# You can set up your permission plugin with this special group to have no permissions,
# or only permission to chat (or permission to send private messages etc.).
# The better way is to set up this group with few permissions, so if a player
# tries to exploit an account they can do only what you've defined for the group.
# After login, the player will be moved to his correct permissions group!
# Please note that the group name is case-sensitive, so 'admin' is different from 'Admin'
# Otherwise your group will be wiped and the player will join in the default group []!
# Example: registeredPlayerGroup: 'NotLogged'
registeredPlayerGroup: ''
# Similar to above, unregistered players can be set to the following
# permissions group
unregisteredPlayerGroup: ''
Email:
# Email SMTP server host
mailSMTP: 'smtp.gmail.com'
# Email SMTP server port
mailPort: 465
# Only affects port 25: enable TLS/STARTTLS?
useTls: true
# Email account which sends the mails
mailAccount: ''
# Email account password
@ -366,18 +358,13 @@ Hooks:
disableSocialSpy: false
# Do we need to force /motd Essentials command on join?
useEssentialsMotd: false
GroupOptions:
# Unregistered permission group
UnregisteredPlayerGroup: ''
# Registered permission group
RegisteredPlayerGroup: ''
Protection:
# Enable some servers protection (country based login, antibot)
enableProtection: false
# Apply the protection also to registered usernames
enableProtectionRegistered: true
# Countries allowed to join the server and register. For country codes, see
# http://dev.bukkit.org/bukkit-plugins/authme-reloaded/pages/countries-codes/
# https://dev.bukkit.org/projects/authme-reloaded/pages/countries-codes
# PLEASE USE QUOTES!
countries:
- 'US'
@ -432,6 +419,8 @@ Security:
maxLoginTry: 5
# Captcha length
captchaLength: 5
# Minutes after which login attempts count is reset for a player
captchaCountReset: 60
tempban:
# Tempban a user's IP address if they enter the wrong password too many times
enableTempban: false
@ -448,6 +437,53 @@ Security:
length: 8
# How many hours is a recovery code valid for?
validForHours: 4
# Max number of tries to enter recovery code
maxTries: 3
# How long a player has after password recovery to change their password
# without logging in. This is in minutes.
# Default: 2 minutes
passwordChangeTimeout: 2
emailRecovery:
# Seconds a user has to wait for before a password recovery mail may be sent again
# This prevents an attacker from abusing AuthMe's email feature.
cooldown: 60
# Before a user logs in, various properties are temporarily removed from the player,
# such as OP status, ability to fly, and walk/fly speed.
# Once the user is logged in, we add back the properties we previously saved.
# In this section, you may define how these properties should be handled.
limbo:
persistence:
# Besides storing the data in memory, you can define if/how the data should be persisted
# on disk. This is useful in case of a server crash, so next time the server starts we can
# properly restore things like OP status, ability to fly, and walk/fly speed.
# DISABLED: no disk storage, INDIVIDUAL_FILES: each player data in its own file,
# SINGLE_FILE: all data in one single file (only if you have a small server!)
# SEGMENT_FILES: distributes players into different buckets based on their UUID. See below.
type: 'INDIVIDUAL_FILES'
# This setting only affects SEGMENT_FILES persistence. The segment file
# persistence attempts to reduce the number of files by distributing players into various
# buckets based on their UUID. This setting defines into how many files the players should
# be distributed. Possible values: ONE, FOUR, EIGHT, SIXTEEN, THIRTY_TWO, SIXTY_FOUR,
# ONE_TWENTY for 128, TWO_FIFTY for 256.
# For example, if you expect 100 non-logged in players, setting to SIXTEEN will average
# 6.25 players per file (100 / 16). If you set to ONE, like persistence SINGLE_FILE, only
# one file will be used. Contrary to SINGLE_FILE, it won't keep the entries in cache, which
# may deliver different results in terms of performance.
# Note: if you change this setting all data will be migrated. If you have a lot of data,
# change this setting only on server restart, not with /authme reload.
segmentDistribution: 'SIXTEEN'
# Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE.
# RESTORE sets back the old property from the player.
restoreAllowFlight: 'RESTORE'
# Restore fly speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO.
# RESTORE: restore the speed the player had;
# DEFAULT: always set to default speed;
# MAX_RESTORE: take the maximum of the player's current speed and the previous one
# RESTORE_NO_ZERO: Like 'restore' but sets speed to default if the player's speed was 0
restoreFlySpeed: 'MAX_RESTORE'
# Restore walk speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO.
# See above for a description of the values.
restoreWalkSpeed: 'MAX_RESTORE'
BackupSystem:
# Enable or disable automatic backup
ActivateBackup: false
@ -457,6 +493,17 @@ BackupSystem:
OnServerStop: true
# Windows only mysql installation Path
MysqlWindowsPath: 'C:\Program Files\MySQL\MySQL Server 5.1\'
Converter:
Rakamak:
# Rakamak file name
fileName: 'users.rak'
# Rakamak use IP?
useIP: false
# Rakamak IP file name
ipFileName: 'UsersIp.rak'
CrazyLogin:
# CrazyLogin database file name
fileName: 'accounts.db'
```
To change settings on a running server, save your changes to config.yml and use
@ -464,4 +511,4 @@ To change settings on a running server, save your changes to config.yml and use
---
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Jan 14 22:12:16 CET 2017
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed Mar 22 23:10:33 CET 2017

View File

@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Fri Nov 25 15:48:35 CET 2016. See docs/hashmethods/hash_algorithms.tpl.md -->
<!-- File auto-generated on Sat Mar 25 00:15:27 CET 2017. See docs/hashmethods/hash_algorithms.tpl.md -->
## Hash Algorithms
AuthMe supports the following hash algorithms for storing your passwords safely.
@ -10,11 +10,11 @@ Algorithm | Recommendation | Hash length | ASCII | | Salt type | Length | Se
BCRYPT | Recommended | 60 | | | Text | |
BCRYPT2Y | Recommended | 60 | | | Text | 22 |
CRAZYCRYPT1 | Do not use | 128 | | | Username | |
DOUBLEMD5 | Do not use | 32 | | | None | |
DOUBLEMD5 | Deprecated | 32 | | | None | |
IPB3 | Acceptable | 32 | | | Text | 5 | Y
IPB4 | Does not work | 60 | | | Text | 22 | Y
JOOMLA | Acceptable | 65 | | | Text | 32 |
MD5 | Do not use | 32 | | | None | |
MD5 | Deprecated | 32 | | | None | |
MD5VB | Acceptable | 56 | | | Text | 16 |
MYBB | Acceptable | 32 | | | Text | 8 | Y
PBKDF2 | Recommended | 165 | | | Text | 16 |
@ -24,14 +24,14 @@ PHPFUSION | Do not use | 64 | Y | | | | Y
ROYALAUTH | Do not use | 128 | | | None | |
SALTED2MD5 | Acceptable | 32 | | | Text | | Y
SALTEDSHA512 | Recommended | 128 | | | | | Y
SHA1 | Do not use | 40 | | | None | |
SHA1 | Deprecated | 40 | | | None | |
SHA256 | Recommended | 86 | | | Text | 16 |
SHA512 | Do not use | 128 | | | None | |
SHA512 | Deprecated | 128 | | | None | |
SMF | Do not use | 40 | | | Username | |
TWO_FACTOR | Does not work | 16 | | | None | |
WBB3 | Acceptable | 40 | | | Text | 40 | Y
WBB4 | Recommended | 60 | | | Text | 8 |
WHIRLPOOL | Do not use | 128 | | | None | |
WHIRLPOOL | Deprecated | 128 | | | None | |
WORDPRESS | Acceptable | 34 | | | Text | 9 |
XAUTH | Recommended | 140 | | | Text | 12 |
XFBCRYPT | | 60 | | | | |
@ -82,4 +82,4 @@ or bad.
---
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Fri Nov 25 15:48:35 CET 2016
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat Mar 25 00:15:27 CET 2017

View File

@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Wed Jan 11 21:24:50 CET 2017. See docs/translations/translations.tpl.md -->
<!-- File auto-generated on Wed Mar 22 23:10:32 CET 2017. See docs/translations/translations.tpl.md -->
# AuthMe Translations
The following translations are available in AuthMe. Set `messagesLanguage` to the language code
@ -8,35 +8,34 @@ in your config.yml to use the language, or use another language code to start a
Code | Language | Translated | &nbsp;
---- | -------- | ---------: | ------
[en](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_en.yml) | English | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 69% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=69&h=5&txtpad=1" alt="bar" />
[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 62% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=62&h=5&txtpad=1" alt="bar" />
[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 66% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=66&h=5&txtpad=1" alt="bar" />
[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 70% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=70&h=5&txtpad=1" alt="bar" />
[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 99% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ee55&w=99&h=5&txtpad=1" alt="bar" />
[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 70% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=70&h=5&txtpad=1" alt="bar" />
[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 72% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=72&h=5&txtpad=1" alt="bar" />
[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 53% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb6600&w=53&h=5&txtpad=1" alt="bar" />
[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 77% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb9900&w=77&h=5&txtpad=1" alt="bar" />
[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 86% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=86&h=5&txtpad=1" alt="bar" />
[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 99% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ee55&w=99&h=5&txtpad=1" alt="bar" />
[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 99% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ee55&w=99&h=5&txtpad=1" alt="bar" />
[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 46% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa5500&w=46&h=5&txtpad=1" alt="bar" />
[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 81% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=81&h=5&txtpad=1" alt="bar" />
[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 93% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=93&h=5&txtpad=1" alt="bar" />
[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 81% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=81&h=5&txtpad=1" alt="bar" />
[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 81% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=81&h=5&txtpad=1" alt="bar" />
[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 96% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ee55&w=96&h=5&txtpad=1" alt="bar" />
[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 81% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=81&h=5&txtpad=1" alt="bar" />
[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 95% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=95&h=5&txtpad=1" alt="bar" />
[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 85% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=85&h=5&txtpad=1" alt="bar" />
[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 85% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=85&h=5&txtpad=1" alt="bar" />
[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 85% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=85&h=5&txtpad=1" alt="bar" />
[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 95% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=95&h=5&txtpad=1" alt="bar" />
[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 53% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb6600&w=53&h=5&txtpad=1" alt="bar" />
[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 56% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb6600&w=56&h=5&txtpad=1" alt="bar" />
[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 95% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=95&h=5&txtpad=1" alt="bar" />
[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 60% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=60&h=5&txtpad=1" alt="bar" />
[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 84% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=84&h=5&txtpad=1" alt="bar" />
[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 60% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=60&h=5&txtpad=1" alt="bar" />
[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 95% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=95&h=5&txtpad=1" alt="bar" />
[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 61% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=61&h=5&txtpad=1" alt="bar" />
[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 45% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa5500&w=45&h=5&txtpad=1" alt="bar" />
[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 85% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=85&h=5&txtpad=1" alt="bar" />
[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 95% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=95&h=5&txtpad=1" alt="bar" />
[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 95% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=95&h=5&txtpad=1" alt="bar" />
[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 84% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=84&h=5&txtpad=1" alt="bar" />
[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 95% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=95&h=5&txtpad=1" alt="bar" />
[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 39% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa4400&w=39&h=5&txtpad=1" alt="bar" />
[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 95% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=95&h=5&txtpad=1" alt="bar" />
[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 79% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb9900&w=79&h=5&txtpad=1" alt="bar" />
[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 85% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=85&h=5&txtpad=1" alt="bar" />
[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 95% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=95&h=5&txtpad=1" alt="bar" />
[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 69% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=69&h=5&txtpad=1" alt="bar" />
[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 82% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=82&h=5&txtpad=1" alt="bar" />
[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 69% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=69&h=5&txtpad=1" alt="bar" />
---
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed Jan 11 21:24:50 CET 2017
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed Mar 22 23:10:32 CET 2017

58
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>fr.xephi</groupId>
<artifactId>authme</artifactId>
<version>5.2</version>
<version>5.3-SNAPSHOT</version>
<name>AuthMeReloaded</name>
<description>The first authentication plugin for the Bukkit API!</description>
@ -108,10 +108,6 @@
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</exclusion>
<exclusion>
<artifactId>json-simple</artifactId>
<groupId>com.googlecode.json-simple</groupId>
</exclusion>
<exclusion>
<artifactId>persistence-api</artifactId>
<groupId>javax.persistence</groupId>
@ -146,10 +142,6 @@
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</exclusion>
<exclusion>
<artifactId>json-simple</artifactId>
<groupId>com.googlecode.json-simple</groupId>
</exclusion>
<exclusion>
<artifactId>persistence-api</artifactId>
<groupId>javax.persistence</groupId>
@ -249,8 +241,8 @@
<shadedPattern>fr.xephi.authme.libs.jalu.injector</shadedPattern>
</relocation>
<relocation>
<pattern>com.github.authme.configme</pattern>
<shadedPattern>fr.xephi.authme.libs.authme.configme</shadedPattern>
<pattern>ch.jalu.configme</pattern>
<shadedPattern>fr.xephi.authme.libs.jalu.configme</shadedPattern>
</relocation>
<relocation>
<pattern>com.zaxxer.hikari</pattern>
@ -276,10 +268,10 @@
<pattern>javax.inject</pattern>
<shadedPattern>fr.xephi.authme.libs.javax.inject</shadedPattern>
</relocation>
<!-- MCStats.org metrics -->
<!-- bStats metrics class -->
<relocation>
<pattern>org.mcstats</pattern>
<shadedPattern>fr.xephi.authme</shadedPattern>
<pattern>org.bstats</pattern>
<shadedPattern>fr.xephi.authme.libs.org.bstats</shadedPattern>
</relocation>
</relocations>
<outputFile>target/${project.finalName}-spigot.jar</outputFile>
@ -331,10 +323,10 @@
<pattern>javax.inject</pattern>
<shadedPattern>fr.xephi.authme.libs.javax.inject</shadedPattern>
</relocation>
<!-- MCStats.org metrics -->
<!-- bStats metrics class -->
<relocation>
<pattern>org.mcstats</pattern>
<shadedPattern>fr.xephi.authme</shadedPattern>
<pattern>org.bstats</pattern>
<shadedPattern>fr.xephi.authme.libs.org.bstats</shadedPattern>
</relocation>
</relocations>
<outputFile>target/${project.finalName}-legacy.jar</outputFile>
@ -442,6 +434,12 @@
<id>xephi-repo</id>
<url>http://ci.xephi.fr/plugin/repository/everything/</url>
</repository>
<!-- bStats Repo -->
<repository>
<id>bstats-repo</id>
<url>http://repo.bstats.org/content/groups/public</url>
</repository>
</repositories>
<dependencies>
@ -451,7 +449,7 @@
<dependency>
<groupId>ch.jalu</groupId>
<artifactId>injector</artifactId>
<version>0.3</version>
<version>0.4</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
@ -513,7 +511,7 @@
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.5.1</version>
<version>2.6.0</version>
<scope>compile</scope>
<exclusions>
<exclusion>
@ -527,7 +525,7 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
<version>1.7.22</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
@ -544,19 +542,11 @@
<!-- Bukkit Libraries -->
<!-- Metrics API -->
<!-- bStats metrics -->
<dependency>
<groupId>org.mcstats.bukkit</groupId>
<artifactId>metrics</artifactId>
<version>R8-SNAPSHOT</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
</exclusion>
</exclusions>
<optional>true</optional>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>1.0</version>
</dependency>
<!-- ProtocolLib -->
@ -902,7 +892,7 @@
<dependency>
<groupId>ch.jalu</groupId>
<artifactId>configme</artifactId>
<version>0.3</version>
<version>0.4</version>
<scope>compile</scope>
<optional>true</optional>
<exclusions>
@ -933,7 +923,7 @@
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
<version>2.4.1</version>
<version>2.7.9</version>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>

View File

@ -14,6 +14,8 @@ import fr.xephi.authme.initialization.OnShutdownPlayerSaver;
import fr.xephi.authme.initialization.OnStartupTasks;
import fr.xephi.authme.initialization.SettingsProvider;
import fr.xephi.authme.initialization.TaskCloser;
import fr.xephi.authme.initialization.factory.FactoryDependencyHandler;
import fr.xephi.authme.initialization.factory.SingletonStoreDependencyHandler;
import fr.xephi.authme.listener.BlockListener;
import fr.xephi.authme.listener.EntityListener;
import fr.xephi.authme.listener.PlayerListener;
@ -24,22 +26,22 @@ import fr.xephi.authme.listener.PlayerListener19;
import fr.xephi.authme.listener.ServerListener;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.permission.PermissionsSystemType;
import fr.xephi.authme.security.crypts.SHA256;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.crypts.Sha256;
import fr.xephi.authme.service.BackupService;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.GeoIpService;
import fr.xephi.authme.service.MigrationService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.task.CleanupTask;
import fr.xephi.authme.task.purge.PurgeService;
import fr.xephi.authme.util.PlayerUtils;
import org.apache.commons.lang.SystemUtils;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
@ -72,8 +74,6 @@ public class AuthMe extends JavaPlugin {
private DataSource database;
private BukkitService bukkitService;
private Injector injector;
private GeoIpService geoIpService;
private PlayerCache playerCache;
/**
* Constructor.
@ -139,6 +139,7 @@ public class AuthMe extends JavaPlugin {
initialize();
} catch (Exception e) {
ConsoleLogger.logException("Aborting initialization of AuthMe:", e);
OnStartupTasks.displayLegacyJarHint(e);
stopOrUnload();
return;
}
@ -148,7 +149,8 @@ public class AuthMe extends JavaPlugin {
// If server is using PermissionsBukkit, print a warning that some features may not be supported
if (PermissionsSystemType.PERMISSIONS_BUKKIT.equals(permsMan.getPermissionSystem())) {
ConsoleLogger.warning("Warning! This server uses PermissionsBukkit for permissions. Some permissions features may not be supported!");
ConsoleLogger.warning("Warning! This server uses PermissionsBukkit for permissions. Some permissions "
+ "features may not be supported!");
}
// Do a backup on start
@ -159,10 +161,12 @@ public class AuthMe extends JavaPlugin {
// Sponsor messages
ConsoleLogger.info("Development builds are available on our jenkins, thanks to f14stelt.");
ConsoleLogger.info("Do you want a good game server? Look at our sponsor GameHosting.it leader in Italy as Game Server Provider!");
ConsoleLogger.info("Do you want a good game server? Look at our sponsor GameHosting.it leader "
+ "in Italy as Game Server Provider!");
// Successful message
ConsoleLogger.info("AuthMe " + getPluginVersion() + " build n." + getPluginBuildNumber() + " correctly enabled!");
ConsoleLogger.info("AuthMe " + getPluginVersion() + " build n." + getPluginBuildNumber()
+ " correctly enabled!");
// Purge on start if enabled
PurgeService purgeService = injector.getSingleton(PurgeService.class);
@ -197,11 +201,19 @@ public class AuthMe extends JavaPlugin {
ConsoleLogger.setLogger(getLogger());
ConsoleLogger.setLogFile(new File(getDataFolder(), LOG_FILENAME));
// Check java version
if(!SystemUtils.isJavaVersionAtLeast(1.8f)) {
throw new IllegalStateException("You need Java 1.8 or above to run this plugin!");
}
// Create plugin folder
getDataFolder().mkdir();
// Create injector, provide elements from the Bukkit environment and register providers
injector = new InjectorBuilder().addDefaultHandlers("fr.xephi.authme").create();
injector = new InjectorBuilder()
.addHandlers(new FactoryDependencyHandler(), new SingletonStoreDependencyHandler())
.addDefaultHandlers("fr.xephi.authme")
.create();
injector.register(AuthMe.class, this);
injector.register(Server.class, getServer());
injector.register(PluginManager.class, getServer().getPluginManager());
@ -219,7 +231,7 @@ public class AuthMe extends JavaPlugin {
instantiateServices(injector);
// Convert deprecated PLAINTEXT hash entries
MigrationService.changePlainTextToSha256(settings, database, new SHA256());
MigrationService.changePlainTextToSha256(settings, database, new Sha256());
// TODO: does this still make sense? -sgdc3
// If the server is empty (fresh start) just set all the players as unlogged
@ -240,16 +252,15 @@ public class AuthMe extends JavaPlugin {
*
* @param injector the injector
*/
protected void instantiateServices(Injector injector) {
void instantiateServices(Injector injector) {
// PlayerCache is still injected statically sometimes
playerCache = PlayerCache.getInstance();
PlayerCache playerCache = PlayerCache.getInstance();
injector.register(PlayerCache.class, playerCache);
database = injector.getSingleton(DataSource.class);
permsMan = injector.getSingleton(PermissionsManager.class);
bukkitService = injector.getSingleton(BukkitService.class);
commandHandler = injector.getSingleton(CommandHandler.class);
geoIpService = injector.getSingleton(GeoIpService.class);
// Trigger construction of API classes; they will keep track of the singleton
injector.getSingleton(NewAPI.class);
@ -270,6 +281,19 @@ public class AuthMe extends JavaPlugin {
&& settings.getProperty(PluginSettings.SESSIONS_ENABLED)) {
ConsoleLogger.warning("WARNING!!! You set session timeout to 0, this may cause security issues!");
}
// Use TLS property only affects port 25
if (!settings.getProperty(EmailSettings.PORT25_USE_TLS)
&& settings.getProperty(EmailSettings.SMTP_PORT) != 25) {
ConsoleLogger.warning("Note: You have set Email.useTls to false but this only affects mail over port 25");
}
// Unsalted hashes will be deprecated in 5.4 (see Github issue #1016)
HashAlgorithm hash = settings.getProperty(SecuritySettings.PASSWORD_HASH);
if (OnStartupTasks.isHashDeprecatedIn54(hash)) {
ConsoleLogger.warning("You are using an unsalted hash (" + hash + "). Support for this will be removed "
+ "in 5.4 -- do you still need it? Comment on https://github.com/Xephi/AuthMeReloaded/issues/1016");
}
}
/**
@ -277,7 +301,7 @@ public class AuthMe extends JavaPlugin {
*
* @param injector the injector
*/
protected void registerEventListeners(Injector injector) {
void registerEventListeners(Injector injector) {
// Get the plugin manager instance
PluginManager pluginManager = getServer().getPluginManager();
@ -344,24 +368,6 @@ public class AuthMe extends JavaPlugin {
ConsoleLogger.close();
}
public String replaceAllInfo(String message, Player player) {
String playersOnline = Integer.toString(bukkitService.getOnlinePlayers().size());
String ipAddress = PlayerUtils.getPlayerIp(player);
Server server = getServer();
return message
.replace("&", "\u00a7")
.replace("{PLAYER}", player.getName())
.replace("{ONLINE}", playersOnline)
.replace("{MAXPLAYERS}", Integer.toString(server.getMaxPlayers()))
.replace("{IP}", ipAddress)
.replace("{LOGINS}", Integer.toString(playerCache.getLogged()))
.replace("{WORLD}", player.getWorld().getName())
.replace("{SERVER}", server.getServerName())
.replace("{VERSION}", server.getBukkitVersion())
// TODO: We should cache info like this, maybe with a class that extends Player?
.replace("{COUNTRY}", geoIpService.getCountryName(ipAddress));
}
/**
* Handle Bukkit commands.
*

View File

@ -12,7 +12,10 @@ import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
@ -89,6 +92,18 @@ public final class ConsoleLogger {
writeLog("[WARN] " + message);
}
/**
* Log a Throwable with the provided message on WARNING level
* and save the stack trace to the log file.
*
* @param message The message to accompany the exception
* @param th The Throwable to log
*/
public static void logException(String message, Throwable th) {
warning(message + " " + StringUtils.formatException(th));
writeLog(Throwables.getStackTraceAsString(th));
}
/**
* Log an INFO message.
*
@ -114,6 +129,10 @@ public final class ConsoleLogger {
}
}
// --------
// Debug log methods
// --------
/**
* Log a DEBUG message if enabled.
* <p>
@ -124,21 +143,78 @@ public final class ConsoleLogger {
*/
public static void debug(String message) {
if (logLevel.includes(LogLevel.DEBUG)) {
logger.info("Debug: " + message);
writeLog("[DEBUG] " + message);
String debugMessage = "[DEBUG] " + message;
logger.info(debugMessage);
writeLog(debugMessage);
}
}
/**
* Log a Throwable with the provided message on WARNING level
* and save the stack trace to the log file.
* Log the DEBUG message from the supplier if enabled.
*
* @param message The message to accompany the exception
* @param th The Throwable to log
* @param msgSupplier the message supplier
*/
public static void logException(String message, Throwable th) {
warning(message + " " + StringUtils.formatException(th));
writeLog(Throwables.getStackTraceAsString(th));
public static void debug(Supplier<String> msgSupplier) {
if (logLevel.includes(LogLevel.DEBUG)) {
String debugMessage = "[DEBUG] " + msgSupplier.get();
logger.info(debugMessage);
writeLog(debugMessage);
}
}
/**
* Log the DEBUG message.
*
* @param message the message
* @param param1 parameter to replace in the message
*/
public static void debug(String message, String param1) {
if (logLevel.includes(LogLevel.DEBUG)) {
String debugMessage = "[DEBUG] " + message;
logger.log(Level.INFO, debugMessage, param1);
writeLog(debugMessage + " {" + param1 + "}");
}
}
/**
* Log the DEBUG message.
*
* @param message the message
* @param param1 first param to replace in message
* @param param2 second param to replace in message
*/
// Avoids array creation if DEBUG level is disabled
public static void debug(String message, String param1, String param2) {
if (logLevel.includes(LogLevel.DEBUG)) {
debug(message, new String[]{param1, param2});
}
}
/**
* Log the DEBUG message.
*
* @param message the message
* @param params the params to replace in the message
*/
// Equivalent to debug(String, Object...) but avoids conversions
public static void debug(String message, String... params) {
if (logLevel.includes(LogLevel.DEBUG)) {
String debugMessage = "[DEBUG] " + message;
logger.log(Level.INFO, debugMessage, params);
writeLog(debugMessage + " {" + String.join(", ", params) + "}");
}
}
/**
* Log the DEBUG message.
*
* @param message the message
* @param params the params to replace in the message
*/
public static void debug(String message, Object... params) {
if (logLevel.includes(LogLevel.DEBUG)) {
debug(message, Arrays.stream(params).map(String::valueOf).toArray(String[]::new));
}
}

View File

@ -23,6 +23,7 @@ import javax.inject.Inject;
* @deprecated Use {@link NewAPI}
*/
@Deprecated
@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) // Justification: Class name cannot be changed anymore
public class API {
private static AuthMe instance;

View File

@ -5,7 +5,8 @@ import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider;
import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams;
import fr.xephi.authme.process.register.executors.RegistrationMethod;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.PluginHookService;
@ -24,6 +25,7 @@ import java.util.List;
* NewAPI authmeApi = AuthMe.getApi();
* </code>
*/
@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) // Justification: Class name cannot be changed anymore
public class NewAPI {
private static NewAPI singleton;
@ -34,15 +36,13 @@ public class NewAPI {
private final Management management;
private final ValidationService validationService;
private final PlayerCache playerCache;
private final RegistrationExecutorProvider registrationExecutorProvider;
/*
* Constructor for NewAPI.
*/
@Inject
NewAPI(AuthMe plugin, PluginHookService pluginHookService, DataSource dataSource, PasswordSecurity passwordSecurity,
Management management, ValidationService validationService, PlayerCache playerCache,
RegistrationExecutorProvider registrationExecutorProvider) {
Management management, ValidationService validationService, PlayerCache playerCache) {
this.plugin = plugin;
this.pluginHookService = pluginHookService;
this.dataSource = dataSource;
@ -50,7 +50,6 @@ public class NewAPI {
this.management = management;
this.validationService = validationService;
this.playerCache = playerCache;
this.registrationExecutorProvider = registrationExecutorProvider;
NewAPI.singleton = this;
}
@ -128,7 +127,8 @@ public class NewAPI {
public Location getLastLocation(Player player) {
PlayerAuth auth = playerCache.getAuth(player.getName());
if (auth != null) {
return new Location(Bukkit.getWorld(auth.getWorld()), auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ());
return new Location(Bukkit.getWorld(auth.getWorld()),
auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ());
}
return null;
}
@ -203,8 +203,8 @@ public class NewAPI {
* @param autoLogin Should the player be authenticated automatically after the registration?
*/
public void forceRegister(Player player, String password, boolean autoLogin) {
management.performRegister(player,
registrationExecutorProvider.getPasswordRegisterExecutor(player, password, autoLogin));
management.performRegister(RegistrationMethod.API_REGISTRATION,
ApiPasswordRegisterParams.of(player, password, autoLogin));
}
/**

View File

@ -1,8 +1,8 @@
package fr.xephi.authme.command;
import fr.xephi.authme.permission.PermissionNode;
import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Utils;
import java.util.ArrayList;
import java.util.List;
@ -19,6 +19,7 @@ import static java.util.Arrays.asList;
* {@code /authme} has a child whose label is {@code "register"}, then {@code /authme register} is the command that
* the child defines.
*/
@SuppressWarnings("checkstyle:FinalClass") // Justification: class is mocked in multiple tests
public class CommandDescription {
/**
@ -224,7 +225,7 @@ public class CommandDescription {
* @return The generated CommandDescription object
*/
public CommandDescription build() {
checkArgument(!CollectionUtils.isEmpty(labels), "Labels may not be empty");
checkArgument(!Utils.isCollectionEmpty(labels), "Labels may not be empty");
checkArgument(!StringUtils.isEmpty(description), "Description may not be empty");
checkArgument(!StringUtils.isEmpty(detailedDescription), "Detailed description may not be empty");
checkArgument(executableCommand != null, "Executable command must be set");

View File

@ -1,8 +1,8 @@
package fr.xephi.authme.command;
import ch.jalu.injector.Injector;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.help.HelpProvider;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.permission.PermissionsManager;
@ -40,13 +40,13 @@ public class CommandHandler {
private Map<Class<? extends ExecutableCommand>, ExecutableCommand> commands = new HashMap<>();
@Inject
CommandHandler(Injector injector, CommandMapper commandMapper, PermissionsManager permissionsManager,
Messages messages, HelpProvider helpProvider) {
CommandHandler(Factory<ExecutableCommand> commandFactory, CommandMapper commandMapper,
PermissionsManager permissionsManager, Messages messages, HelpProvider helpProvider) {
this.commandMapper = commandMapper;
this.permissionsManager = permissionsManager;
this.messages = messages;
this.helpProvider = helpProvider;
initializeCommands(injector, commandMapper.getCommandClasses());
initializeCommands(commandFactory, commandMapper.getCommandClasses());
}
/**
@ -94,13 +94,13 @@ public class CommandHandler {
/**
* Initialize all required ExecutableCommand objects.
*
* @param injector the injector
* @param commandFactory factory to create command objects
* @param commandClasses the classes to instantiate
*/
private void initializeCommands(Injector injector,
private void initializeCommands(Factory<ExecutableCommand> commandFactory,
Set<Class<? extends ExecutableCommand>> commandClasses) {
for (Class<? extends ExecutableCommand> clazz : commandClasses) {
commands.put(clazz, injector.newInstance(clazz));
commands.put(clazz, commandFactory.newInstance(clazz));
}
}

View File

@ -24,12 +24,15 @@ import fr.xephi.authme.command.executable.authme.SpawnCommand;
import fr.xephi.authme.command.executable.authme.SwitchAntiBotCommand;
import fr.xephi.authme.command.executable.authme.UnregisterAdminCommand;
import fr.xephi.authme.command.executable.authme.VersionCommand;
import fr.xephi.authme.command.executable.authme.debug.DebugCommand;
import fr.xephi.authme.command.executable.captcha.CaptchaCommand;
import fr.xephi.authme.command.executable.changepassword.ChangePasswordCommand;
import fr.xephi.authme.command.executable.email.AddEmailCommand;
import fr.xephi.authme.command.executable.email.ChangeEmailCommand;
import fr.xephi.authme.command.executable.email.EmailBaseCommand;
import fr.xephi.authme.command.executable.email.ProcessCodeCommand;
import fr.xephi.authme.command.executable.email.RecoverEmailCommand;
import fr.xephi.authme.command.executable.email.SetPasswordCommand;
import fr.xephi.authme.command.executable.email.ShowEmailCommand;
import fr.xephi.authme.command.executable.login.LoginCommand;
import fr.xephi.authme.command.executable.logout.LogoutCommand;
@ -37,6 +40,7 @@ import fr.xephi.authme.command.executable.register.RegisterCommand;
import fr.xephi.authme.command.executable.unregister.UnregisterCommand;
import fr.xephi.authme.permission.AdminPermission;
import fr.xephi.authme.permission.PlayerPermission;
import fr.xephi.authme.permission.PlayerStatePermission;
import java.util.Arrays;
import java.util.Collection;
@ -62,6 +66,10 @@ public class CommandInitializer {
return commands;
}
/**
* Builds the command description objects for all available AuthMe commands.
*/
@SuppressWarnings({"checkstyle:LocalVariableName", "checkstyle:AbbreviationAsWordInName"})
private void buildCommands() {
// Register the base AuthMe Reloaded command
final CommandDescription AUTHME_BASE = CommandDescription.builder()
@ -283,8 +291,8 @@ public class CommandInitializer {
.labels("converter", "convert", "conv")
.description("Converter command")
.detailedDescription("Converter command for AuthMeReloaded.")
.withArgument("job", "Conversion job: xauth / crazylogin / rakamak / " +
"royalauth / vauth / sqliteToSql / mysqlToSqlite", false)
.withArgument("job", "Conversion job: xauth / crazylogin / rakamak / "
+ "royalauth / vauth / sqliteToSql / mysqlToSqlite", false)
.permission(AdminPermission.CONVERTER)
.executableCommand(ConverterCommand.class)
.register();
@ -298,6 +306,19 @@ public class CommandInitializer {
.executableCommand(MessagesCommand.class)
.register();
CommandDescription.builder()
.parent(AUTHME_BASE)
.labels("debug", "dbg")
.description("Debug features")
.detailedDescription("Allows various operations for debugging.")
.withArgument("child", "The child to execute", true)
.withArgument(".", "meaning varies", true)
.withArgument(".", "meaning varies", true)
.withArgument(".", "meaning varies", true)
.permission(PlayerStatePermission.DEBUG_COMMAND)
.executableCommand(DebugCommand.class)
.register();
// Register the base login command
final CommandDescription LOGIN_BASE = CommandDescription.builder()
.parent(null)
@ -401,14 +422,35 @@ public class CommandInitializer {
.parent(EMAIL_BASE)
.labels("recover", "recovery", "recoveremail", "recovermail")
.description("Recover password using email")
.detailedDescription("Recover your account using an Email address by sending a mail containing " +
"a new password.")
.detailedDescription("Recover your account using an Email address by sending a mail containing "
+ "a new password.")
.withArgument("email", "Email address", false)
.withArgument("code", "Recovery code", true)
.permission(PlayerPermission.RECOVER_EMAIL)
.executableCommand(RecoverEmailCommand.class)
.register();
// Register the process recovery code command
CommandDescription.builder()
.parent(EMAIL_BASE)
.labels("code")
.description("Submit code to recover password")
.detailedDescription("Recover your account by submitting a code delivered to your email.")
.withArgument("code", "Recovery code", false)
.permission(PlayerPermission.RECOVER_EMAIL)
.executableCommand(ProcessCodeCommand.class)
.register();
// Register the change password after recovery command
CommandDescription.builder()
.parent(EMAIL_BASE)
.labels("setpassword")
.description("Set new password after recovery")
.detailedDescription("Set a new password after successfully recovering your account.")
.withArgument("password", "New password", false)
.permission(PlayerPermission.RECOVER_EMAIL)
.executableCommand(SetPasswordCommand.class)
.register();
// Register the base captcha command
CommandDescription CAPTCHA_BASE = CommandDescription.builder()
.parent(null)

View File

@ -2,8 +2,8 @@ package fr.xephi.authme.command;
import fr.xephi.authme.command.executable.HelpCommand;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Utils;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
@ -45,7 +45,7 @@ public class CommandMapper {
* @return The generated {@link FoundCommandResult}
*/
public FoundCommandResult mapPartsToCommand(CommandSender sender, final List<String> parts) {
if (CollectionUtils.isEmpty(parts)) {
if (Utils.isCollectionEmpty(parts)) {
return new FoundCommandResult(null, parts, null, 0.0, MISSING_BASE_COMMAND);
}
@ -123,6 +123,9 @@ public class CommandMapper {
private CommandDescription getBaseCommand(String label) {
String baseLabel = label.toLowerCase();
if (baseLabel.startsWith("authme:")) {
baseLabel = baseLabel.substring("authme:".length());
}
for (CommandDescription command : baseCommands) {
if (command.hasLabel(baseLabel)) {
return command;
@ -142,7 +145,7 @@ public class CommandMapper {
* @return A command if there was a complete match (including proper argument count), null otherwise
*/
private static CommandDescription getSuitableChild(CommandDescription baseCommand, List<String> parts) {
if (CollectionUtils.isEmpty(parts)) {
if (Utils.isCollectionEmpty(parts)) {
return null;
}

View File

@ -19,7 +19,7 @@ public interface ExecutableCommand {
void executeCommand(CommandSender sender, List<String> arguments);
/**
* Returns the message to show to the user if the command is used with the wrong commands.
* Returns the message to show to the user if the command is used with the wrong arguments.
* If null is returned, the standard help (/<i>command</i> help) output is shown.
*
* @return the message explaining the command's usage, or {@code null} for default behavior

View File

@ -1,6 +1,5 @@
package fr.xephi.authme.command.executable.authme;
import ch.jalu.injector.Injector;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import fr.xephi.authme.ConsoleLogger;
@ -11,8 +10,9 @@ import fr.xephi.authme.datasource.converter.MySqlToSqlite;
import fr.xephi.authme.datasource.converter.RakamakConverter;
import fr.xephi.authme.datasource.converter.RoyalAuthConverter;
import fr.xephi.authme.datasource.converter.SqliteToSql;
import fr.xephi.authme.datasource.converter.vAuthConverter;
import fr.xephi.authme.datasource.converter.xAuthConverter;
import fr.xephi.authme.datasource.converter.VAuthConverter;
import fr.xephi.authme.datasource.converter.XAuthConverter;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;
@ -37,7 +37,7 @@ public class ConverterCommand implements ExecutableCommand {
private BukkitService bukkitService;
@Inject
private Injector injector;
private Factory<Converter> converterFactory;
@Override
public void executeCommand(final CommandSender sender, List<String> arguments) {
@ -52,7 +52,7 @@ public class ConverterCommand implements ExecutableCommand {
}
// Get the proper converter instance
final Converter converter = injector.newInstance(converterClass);
final Converter converter = converterFactory.newInstance(converterClass);
// Run the convert job
bukkitService.runTaskAsynchronously(new Runnable() {
@ -78,11 +78,11 @@ public class ConverterCommand implements ExecutableCommand {
*/
private static Map<String, Class<? extends Converter>> getConverters() {
return ImmutableMap.<String, Class<? extends Converter>>builder()
.put("xauth", xAuthConverter.class)
.put("xauth", XAuthConverter.class)
.put("crazylogin", CrazyLoginConverter.class)
.put("rakamak", RakamakConverter.class)
.put("royalauth", RoyalAuthConverter.class)
.put("vauth", vAuthConverter.class)
.put("vauth", VAuthConverter.class)
.put("sqlitetosql", SqliteToSql.class)
.put("mysqltosqlite", MySqlToSqlite.class)
.build();

View File

@ -1,9 +1,8 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.security.PasswordSecurity;
@ -38,9 +37,6 @@ public class RegisterAdminCommand implements ExecutableCommand {
@Inject
private ValidationService validationService;
@Inject
private LimboCache limboCache;
@Override
public void executeCommand(final CommandSender sender, List<String> arguments) {
// Get the player name and password
@ -83,7 +79,6 @@ public class RegisterAdminCommand implements ExecutableCommand {
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() {
@Override
public void run() {
limboCache.restoreData(player);
player.kickPlayer(commonService.retrieveSingleMessage(MessageKey.KICK_FOR_ADMIN_REGISTER));
}
});

View File

@ -1,20 +1,20 @@
package fr.xephi.authme.command.executable.authme;
import ch.jalu.injector.Injector;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.initialization.factory.SingletonStore;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.util.Utils;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.Collection;
import java.util.List;
/**
@ -25,9 +25,6 @@ public class ReloadCommand implements ExecutableCommand {
@Inject
private AuthMe plugin;
@Inject
private Injector injector;
@Inject
private Settings settings;
@ -37,6 +34,12 @@ public class ReloadCommand implements ExecutableCommand {
@Inject
private CommonService commonService;
@Inject
private SingletonStore<Reloadable> reloadableStore;
@Inject
private SingletonStore<SettingsDependent> settingsDependentStore;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
try {
@ -44,8 +47,7 @@ public class ReloadCommand implements ExecutableCommand {
ConsoleLogger.setLoggingOptions(settings);
// We do not change database type for consistency issues, but we'll output a note in the logs
if (!settings.getProperty(DatabaseSettings.BACKEND).equals(dataSource.getType())) {
ConsoleLogger.info("Note: cannot change database type during /authme reload");
sender.sendMessage("Note: cannot change database type during /authme reload");
Utils.logAndSendMessage(sender, "Note: cannot change database type during /authme reload");
}
performReloadOnServices();
commonService.send(sender, MessageKey.CONFIG_RELOAD_SUCCESS);
@ -57,14 +59,10 @@ public class ReloadCommand implements ExecutableCommand {
}
private void performReloadOnServices() {
Collection<Reloadable> reloadables = injector.retrieveAllOfType(Reloadable.class);
for (Reloadable reloadable : reloadables) {
reloadable.reload();
}
reloadableStore.retrieveAllOfType()
.forEach(r -> r.reload());
Collection<SettingsDependent> settingsDependents = injector.retrieveAllOfType(SettingsDependent.class);
for (SettingsDependent dependent : settingsDependents) {
dependent.reload(settings);
}
settingsDependentStore.retrieveAllOfType()
.forEach(s -> s.reload(settings));
}
}

View File

@ -31,12 +31,12 @@ public class VersionCommand implements ExecutableCommand {
printDeveloper(sender, "games647", "games647", "Developer", onlinePlayers);
printDeveloper(sender, "Tim Visee", "timvisee", "Developer", onlinePlayers);
printDeveloper(sender, "Gabriele C.", "sgdc3", "Project manager, Contributor", onlinePlayers);
sender.sendMessage(ChatColor.GOLD + "Website: " + ChatColor.WHITE +
"http://dev.bukkit.org/bukkit-plugins/authme-reloaded/");
sender.sendMessage(ChatColor.GOLD + "Website: " + ChatColor.WHITE
+ "http://dev.bukkit.org/bukkit-plugins/authme-reloaded/");
sender.sendMessage(ChatColor.GOLD + "License: " + ChatColor.WHITE + "GNU GPL v3.0"
+ ChatColor.GRAY + ChatColor.ITALIC + " (See LICENSE file)");
sender.sendMessage(ChatColor.GOLD + "Copyright: " + ChatColor.WHITE
+ "Copyright (c) AuthMe-Team 2016. All rights reserved.");
+ "Copyright (c) AuthMe-Team 2017. All rights reserved.");
}
/**

View File

@ -0,0 +1,77 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.service.GeoIpService;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.settings.properties.ProtectionSettings;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
import java.util.regex.Pattern;
/**
* Shows the GeoIP information as returned by the geoIpService.
*/
class CountryLookup implements DebugSection {
private static final Pattern IS_IP_ADDR = Pattern.compile("(\\d{1,3}\\.){3}\\d{1,3}");
@Inject
private GeoIpService geoIpService;
@Inject
private DataSource dataSource;
@Inject
private ValidationService validationService;
@Override
public String getName() {
return "cty";
}
@Override
public String getDescription() {
return "Check country protection / country data";
}
@Override
public void execute(CommandSender sender, List<String> arguments) {
if (arguments.isEmpty()) {
sender.sendMessage("Check player: /authme debug cty Bobby");
sender.sendMessage("Check IP address: /authme debug cty 127.123.45.67");
return;
}
String argument = arguments.get(0);
if (IS_IP_ADDR.matcher(argument).matches()) {
outputInfoForIpAddr(sender, argument);
} else {
outputInfoForPlayer(sender, argument);
}
}
private void outputInfoForIpAddr(CommandSender sender, String ipAddr) {
sender.sendMessage("IP '" + ipAddr + "' maps to country '" + geoIpService.getCountryCode(ipAddr)
+ "' (" + geoIpService.getCountryName(ipAddr) + ")");
if (validationService.isCountryAdmitted(ipAddr)) {
sender.sendMessage(ChatColor.DARK_GREEN + "This IP address' country is not blocked");
} else {
sender.sendMessage(ChatColor.DARK_RED + "This IP address' country is blocked from the server");
}
sender.sendMessage("Note: if " + ProtectionSettings.ENABLE_PROTECTION + " is false no country is blocked");
}
private void outputInfoForPlayer(CommandSender sender, String name) {
PlayerAuth auth = dataSource.getAuth(name);
if (auth == null) {
sender.sendMessage("No player with name '" + name + "'");
} else {
sender.sendMessage("Player '" + name + "' has IP address " + auth.getIp());
outputInfoForIpAddr(sender, auth.getIp());
}
}
}

View File

@ -0,0 +1,72 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.datasource.CacheDataSource;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.initialization.factory.SingletonStore;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
import java.util.Map;
import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.applyToLimboPlayersMap;
/**
* Fetches various statistics, particularly regarding in-memory data that is stored.
*/
class DataStatistics implements DebugSection {
@Inject
private PlayerCache playerCache;
@Inject
private LimboService limboService;
@Inject
private DataSource dataSource;
@Inject
private SingletonStore<Object> singletonStore;
@Override
public String getName() {
return "stats";
}
@Override
public String getDescription() {
return "Outputs general data statistics";
}
@Override
public void execute(CommandSender sender, List<String> arguments) {
sender.sendMessage("LimboPlayers in memory: " + applyToLimboPlayersMap(limboService, Map::size));
sender.sendMessage("PlayerCache size: " + playerCache.getLogged() + " (= logged in players)");
outputDatabaseStats(sender);
outputInjectorStats(sender);
}
private void outputDatabaseStats(CommandSender sender) {
sender.sendMessage("Total players in DB: " + dataSource.getAccountsRegistered());
sender.sendMessage("Total marked as logged in in DB: " + dataSource.getLoggedPlayers().size());
if (dataSource instanceof CacheDataSource) {
CacheDataSource cacheDataSource = (CacheDataSource) this.dataSource;
sender.sendMessage("Cached PlayerAuth objects: " + cacheDataSource.getCachedAuths().size());
}
}
private void outputInjectorStats(CommandSender sender) {
sender.sendMessage(
String.format("Singleton Java classes: %d (Reloadable: %d / SettingsDependent: %d / HasCleanup: %d)",
singletonStore.retrieveAllOfType().size(),
singletonStore.retrieveAllOfType(Reloadable.class).size(),
singletonStore.retrieveAllOfType(SettingsDependent.class).size(),
singletonStore.retrieveAllOfType(HasCleanup.class).size()));
}
}

View File

@ -0,0 +1,60 @@
package fr.xephi.authme.command.executable.authme.debug;
import com.google.common.collect.ImmutableSet;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.initialization.factory.Factory;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* Debug command main.
*/
public class DebugCommand implements ExecutableCommand {
private static final Set<Class<? extends DebugSection>> SECTION_CLASSES = ImmutableSet.of(
PermissionGroups.class, DataStatistics.class, CountryLookup.class, PlayerAuthViewer.class, InputValidator.class,
LimboPlayerViewer.class, CountryLookup.class, HasPermissionChecker.class, TestEmailSender.class,
SpawnLocationViewer.class);
@Inject
private Factory<DebugSection> debugSectionFactory;
private Map<String, DebugSection> sections;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
DebugSection debugSection = getDebugSection(arguments);
if (debugSection == null) {
sender.sendMessage("Available sections:");
getSections().values()
.forEach(e -> sender.sendMessage("- " + e.getName() + ": " + e.getDescription()));
} else {
debugSection.execute(sender, arguments.subList(1, arguments.size()));
}
}
private DebugSection getDebugSection(List<String> arguments) {
if (arguments.isEmpty()) {
return null;
}
return getSections().get(arguments.get(0).toLowerCase());
}
// Lazy getter
private Map<String, DebugSection> getSections() {
if (sections == null) {
Map<String, DebugSection> sections = new TreeMap<>();
for (Class<? extends DebugSection> sectionClass : SECTION_CLASSES) {
DebugSection section = debugSectionFactory.newInstance(sectionClass);
sections.put(section.getName(), section);
}
this.sections = sections;
}
return sections;
}
}

View File

@ -0,0 +1,30 @@
package fr.xephi.authme.command.executable.authme.debug;
import org.bukkit.command.CommandSender;
import java.util.List;
/**
* A debug section: "child" command of the debug command.
*/
interface DebugSection {
/**
* @return the name to get to this child command
*/
String getName();
/**
* @return short description of the child command
*/
String getDescription();
/**
* Executes the debug child command.
*
* @param sender the sender executing the command
* @param arguments the arguments, without the label of the child command
*/
void execute(CommandSender sender, List<String> arguments);
}

View File

@ -0,0 +1,101 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboService;
import org.bukkit.Location;
import java.lang.reflect.Field;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
/**
* Utilities used within the DebugSection implementations.
*/
final class DebugSectionUtils {
private static Field limboEntriesField;
private DebugSectionUtils() {
}
/**
* Formats the given location in a human readable way. Null-safe.
*
* @param location the location to format
* @return the formatted location
*/
static String formatLocation(Location location) {
if (location == null) {
return "null";
}
String worldName = location.getWorld() == null ? "null" : location.getWorld().getName();
return formatLocation(location.getX(), location.getY(), location.getZ(), worldName);
}
/**
* Formats the given location in a human readable way.
*
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param world the world name
* @return the formatted location
*/
static String formatLocation(double x, double y, double z, String world) {
return "(" + round(x) + ", " + round(y) + ", " + round(z) + ") in '" + world + "'";
}
/**
* Rounds the given number to two decimals.
*
* @param number the number to round
* @return the rounded number
*/
private static String round(double number) {
DecimalFormat df = new DecimalFormat("#.##");
df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.US));
df.setRoundingMode(RoundingMode.HALF_UP);
return df.format(number);
}
private static Field getLimboPlayerEntriesField() {
if (limboEntriesField == null) {
try {
Field field = LimboService.class.getDeclaredField("entries");
field.setAccessible(true);
limboEntriesField = field;
} catch (Exception e) {
ConsoleLogger.logException("Could not retrieve LimboService entries field:", e);
}
}
return limboEntriesField;
}
/**
* Applies the given function to the map in LimboService containing the LimboPlayers.
* As we don't want to expose this information in non-debug settings, this is done with reflection.
* Exceptions are generously caught and {@code null} is returned on failure.
*
* @param limboService the limbo service instance to get the map from
* @param function the function to apply to the map
* @param <U> the result type of the function
*
* @return player names for which there is a LimboPlayer (or error message upon failure)
*/
static <U> U applyToLimboPlayersMap(LimboService limboService, Function<Map, U> function) {
Field limboPlayerEntriesField = getLimboPlayerEntriesField();
if (limboPlayerEntriesField != null) {
try {
return function.apply((Map) limboEntriesField.get(limboService));
} catch (Exception e) {
ConsoleLogger.logException("Could not retrieve LimboService values:", e);
}
}
return null;
}
}

View File

@ -0,0 +1,131 @@
package fr.xephi.authme.command.executable.authme.debug;
import com.google.common.collect.ImmutableList;
import fr.xephi.authme.permission.AdminPermission;
import fr.xephi.authme.permission.DefaultPermission;
import fr.xephi.authme.permission.PermissionNode;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.permission.PlayerPermission;
import fr.xephi.authme.permission.PlayerStatePermission;
import fr.xephi.authme.service.BukkitService;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
/**
* Checks if a player has a given permission, as checked by AuthMe.
*/
class HasPermissionChecker implements DebugSection {
static final List<Class<? extends PermissionNode>> PERMISSION_NODE_CLASSES =
ImmutableList.of(AdminPermission.class, PlayerPermission.class, PlayerStatePermission.class);
@Inject
private PermissionsManager permissionsManager;
@Inject
private BukkitService bukkitService;
@Override
public String getName() {
return "perm";
}
@Override
public String getDescription() {
return "Checks if player has given permission: /authme debug perm bobby my.perm";
}
@Override
public void execute(CommandSender sender, List<String> arguments) {
if (arguments.size() < 2) {
sender.sendMessage("Check if a player has permission:");
sender.sendMessage("Example: /authme debug perm bobby my.perm.node");
return;
}
final String playerName = arguments.get(0);
final String permissionNode = arguments.get(1);
Player player = bukkitService.getPlayerExact(playerName);
if (player == null) {
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName);
if (offlinePlayer == null) {
sender.sendMessage(ChatColor.DARK_RED + "Player '" + playerName + "' does not exist");
} else {
sender.sendMessage("Player '" + playerName + "' not online; checking with offline player");
performPermissionCheck(offlinePlayer, permissionNode, permissionsManager::hasPermissionOffline, sender);
}
} else {
performPermissionCheck(player, permissionNode, permissionsManager::hasPermission, sender);
}
}
/**
* Performs a permission check and informs the given sender of the result. {@code permissionChecker} is the
* permission check to perform with the given {@code node} and the {@code player}.
*
* @param player the player to check a permission for
* @param node the node of the permission to check
* @param permissionChecker permission checking function
* @param sender the sender to inform of the result
* @param <P> the player type
*/
private static <P extends OfflinePlayer> void performPermissionCheck(
P player, String node, BiFunction<P, PermissionNode, Boolean> permissionChecker, CommandSender sender) {
PermissionNode permNode = getPermissionNode(sender, node);
if (permissionChecker.apply(player, permNode)) {
sender.sendMessage(ChatColor.DARK_GREEN + "Success: player '" + player.getName()
+ "' has permission '" + node + "'");
} else {
sender.sendMessage(ChatColor.DARK_RED + "Check failed: player '" + player.getName()
+ "' does NOT have permission '" + node + "'");
}
}
/**
* Based on the given permission node (String), tries to find the according AuthMe {@link PermissionNode}
* instance, or creates a new one if not available.
*
* @param sender the sender (used to inform him if no AuthMe PermissionNode can be matched)
* @param node the node to search for
* @return the node as {@link PermissionNode} object
*/
private static PermissionNode getPermissionNode(CommandSender sender, String node) {
Optional<? extends PermissionNode> permNode = PERMISSION_NODE_CLASSES.stream()
.map(Class::getEnumConstants)
.flatMap(Arrays::stream)
.filter(perm -> perm.getNode().equals(node))
.findFirst();
if (permNode.isPresent()) {
return permNode.get();
} else {
sender.sendMessage("Did not detect AuthMe permission; using default permission = DENIED");
return createPermNode(node);
}
}
private static PermissionNode createPermNode(String node) {
return new PermissionNode() {
@Override
public String getNode() {
return node;
}
@Override
public DefaultPermission getDefaultPermission() {
return DefaultPermission.NOT_ALLOWED;
}
};
}
}

View File

@ -0,0 +1,115 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.listener.FailedVerificationException;
import fr.xephi.authme.listener.OnJoinVerifier;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.service.ValidationService.ValidationResult;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.List;
import static fr.xephi.authme.command.executable.authme.debug.InputValidator.ValidationObject.MAIL;
import static fr.xephi.authme.command.executable.authme.debug.InputValidator.ValidationObject.NAME;
import static fr.xephi.authme.command.executable.authme.debug.InputValidator.ValidationObject.PASS;
/**
* Checks if a sample username, email or password is valid according to the AuthMe settings.
*/
class InputValidator implements DebugSection {
@Inject
private ValidationService validationService;
@Inject
private Messages messages;
@Inject
private OnJoinVerifier onJoinVerifier;
@Override
public String getName() {
return "valid";
}
@Override
public String getDescription() {
return "Check if email / password is valid according to your settings";
}
@Override
public void execute(CommandSender sender, List<String> arguments) {
if (arguments.size() < 2 || !ValidationObject.matchesAny(arguments.get(0))) {
displayUsageHint(sender);
} else if (PASS.matches(arguments.get(0))) {
validatePassword(sender, arguments.get(1));
} else if (MAIL.matches(arguments.get(0))) {
validateEmail(sender, arguments.get(1));
} else if (NAME.matches(arguments.get(0))) {
validateUsername(sender, arguments.get(1));
} else {
throw new IllegalStateException("Unexpected validation object with arg[0] = '" + arguments.get(0) + "'");
}
}
private void displayUsageHint(CommandSender sender) {
sender.sendMessage("You can define forbidden emails and passwords in your config.yml");
sender.sendMessage("This command allows you to test some of the values:");
sender.sendMessage("/authme debug valid pass test1234 -- test if 'test1234' is allowed password");
sender.sendMessage("/authme debug valid mail t@t.tld -- test if 't@t.tld' is allowed email");
sender.sendMessage("/authme debug valid name bobby1 -- test if 'bobby1' is allowed username");
}
private void validatePassword(CommandSender sender, String password) {
ValidationResult validationResult = validationService.validatePassword(password, "");
sender.sendMessage("Validation of password '" + password + "' returned:");
if (validationResult.hasError()) {
messages.send(sender, validationResult.getMessageKey(), validationResult.getArgs());
} else {
sender.sendMessage(ChatColor.DARK_GREEN + "Valid password!");
}
}
private void validateEmail(CommandSender sender, String email) {
boolean isValidEmail = validationService.validateEmail(email);
sender.sendMessage("Validation of email '" + email + "' returned:");
if (isValidEmail) {
sender.sendMessage(ChatColor.DARK_GREEN + "Valid email!");
} else {
sender.sendMessage(ChatColor.DARK_RED + "Email is not valid!");
}
}
private void validateUsername(CommandSender sender, String username) {
sender.sendMessage("Validation of username '" + username + "' returned:");
try {
onJoinVerifier.checkIsValidName(username);
sender.sendMessage("Valid username!");
} catch (FailedVerificationException failedVerificationEx) {
messages.send(sender, failedVerificationEx.getReason(), failedVerificationEx.getArgs());
}
}
enum ValidationObject {
PASS, MAIL, NAME;
static boolean matchesAny(String arg) {
return Arrays.stream(values()).anyMatch(vo -> vo.matches(arg));
}
boolean matches(String arg) {
return name().equalsIgnoreCase(arg);
}
}
}

View File

@ -0,0 +1,132 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.data.limbo.persistence.LimboPersistence;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.service.BukkitService;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation;
import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.applyToLimboPlayersMap;
/**
* Shows the data stored in LimboPlayers and the equivalent properties on online players.
*/
class LimboPlayerViewer implements DebugSection {
@Inject
private LimboService limboService;
@Inject
private LimboPersistence limboPersistence;
@Inject
private BukkitService bukkitService;
@Inject
private PermissionsManager permissionsManager;
@Override
public String getName() {
return "limbo";
}
@Override
public String getDescription() {
return "View LimboPlayers and player's \"limbo stats\"";
}
@Override
public void execute(CommandSender sender, List<String> arguments) {
if (arguments.isEmpty()) {
sender.sendMessage("/authme debug limbo <player>: show a player's limbo info");
sender.sendMessage("Available limbo records: " + applyToLimboPlayersMap(limboService, Map::keySet));
return;
}
LimboPlayer memoryLimbo = limboService.getLimboPlayer(arguments.get(0));
Player player = bukkitService.getPlayerExact(arguments.get(0));
LimboPlayer diskLimbo = player != null ? limboPersistence.getLimboPlayer(player) : null;
if (memoryLimbo == null && player == null) {
sender.sendMessage("No limbo info and no player online with name '" + arguments.get(0) + "'");
return;
}
sender.sendMessage(ChatColor.GOLD + "Showing disk limbo / limbo / player info for '" + arguments.get(0) + "'");
new InfoDisplayer(sender, diskLimbo, memoryLimbo, player)
.sendEntry("Is op", LimboPlayer::isOperator, Player::isOp)
.sendEntry("Walk speed", LimboPlayer::getWalkSpeed, Player::getWalkSpeed)
.sendEntry("Can fly", LimboPlayer::isCanFly, Player::getAllowFlight)
.sendEntry("Fly speed", LimboPlayer::getFlySpeed, Player::getFlySpeed)
.sendEntry("Location", l -> formatLocation(l.getLocation()), p -> formatLocation(p.getLocation()))
.sendEntry("Group", LimboPlayer::getGroup, permissionsManager::getPrimaryGroup);
}
/**
* Displays the info for the given LimboPlayer and Player to the provided CommandSender.
*/
private static final class InfoDisplayer {
private final CommandSender sender;
private final Optional<LimboPlayer> diskLimbo;
private final Optional<LimboPlayer> memoryLimbo;
private final Optional<Player> player;
/**
* Constructor.
*
* @param sender command sender to send the information to
* @param memoryLimbo the limbo player to get data from
* @param player the player to get data from
*/
InfoDisplayer(CommandSender sender, LimboPlayer diskLimbo, LimboPlayer memoryLimbo, Player player) {
this.sender = sender;
this.diskLimbo = Optional.ofNullable(diskLimbo);
this.memoryLimbo = Optional.ofNullable(memoryLimbo);
this.player = Optional.ofNullable(player);
if (memoryLimbo == null) {
sender.sendMessage("Note: no Limbo information available");
}
if (player == null) {
sender.sendMessage("Note: player is not online");
} else if (diskLimbo == null) {
sender.sendMessage("Note: no Limbo on disk available");
}
}
/**
* Displays a piece of information to the command sender.
*
* @param title the designation of the piece of information
* @param limboGetter getter for data retrieval on the LimboPlayer
* @param playerGetter getter for data retrieval on Player
* @param <T> the data type
* @return this instance (for chaining)
*/
<T> InfoDisplayer sendEntry(String title,
Function<LimboPlayer, T> limboGetter,
Function<Player, T> playerGetter) {
sender.sendMessage(
title + ": "
+ getData(diskLimbo, limboGetter)
+ " / "
+ getData(memoryLimbo, limboGetter)
+ " / "
+ getData(player, playerGetter));
return this;
}
static <E, T> String getData(Optional<E> entity, Function<E, T> getter) {
return entity.map(getter).map(String::valueOf).orElse(" -- ");
}
}
}

View File

@ -0,0 +1,41 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.permission.PermissionsManager;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Outputs the permission groups of a player.
*/
class PermissionGroups implements DebugSection {
@Inject
private PermissionsManager permissionsManager;
@Override
public String getName() {
return "groups";
}
@Override
public String getDescription() {
return "Show permission groups a player belongs to";
}
@Override
public void execute(CommandSender sender, List<String> arguments) {
String name = arguments.isEmpty() ? sender.getName() : arguments.get(0);
Player player = Bukkit.getPlayer(name);
if (player == null) {
sender.sendMessage("Player " + name + " could not be found");
} else {
sender.sendMessage("Player " + name + " has permission groups: "
+ String.join(", ", permissionsManager.getGroups(player)));
sender.sendMessage("Primary group is: " + permissionsManager.getGroups(player));
}
}
}

View File

@ -0,0 +1,104 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation;
/**
* Allows to view the data of a PlayerAuth in the database.
*/
class PlayerAuthViewer implements DebugSection {
@Inject
private DataSource dataSource;
@Override
public String getName() {
return "db";
}
@Override
public String getDescription() {
return "View player's data in the database";
}
@Override
public void execute(CommandSender sender, List<String> arguments) {
if (arguments.isEmpty()) {
sender.sendMessage("Enter player name to view his data in the database.");
sender.sendMessage("Example: /authme debug db Bobby");
return;
}
PlayerAuth auth = dataSource.getAuth(arguments.get(0));
if (auth == null) {
sender.sendMessage("No record exists for '" + arguments.get(0) + "'");
} else {
displayAuthToSender(auth, sender);
}
}
/**
* Outputs the PlayerAuth information to the given sender.
*
* @param auth the PlayerAuth to display
* @param sender the sender to send the messages to
*/
private void displayAuthToSender(PlayerAuth auth, CommandSender sender) {
sender.sendMessage(ChatColor.GOLD + "[AuthMe] Player " + auth.getNickname() + " / " + auth.getRealName());
sender.sendMessage("Email: " + auth.getEmail() + ". IP: " + auth.getIp() + ". Group: " + auth.getGroupId());
sender.sendMessage("Quit location: "
+ formatLocation(auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ(), auth.getWorld()));
sender.sendMessage("Last login: " + formatLastLogin(auth));
HashedPassword hashedPass = auth.getPassword();
sender.sendMessage("Hash / salt (partial): '" + safeSubstring(hashedPass.getHash(), 6)
+ "' / '" + safeSubstring(hashedPass.getSalt(), 4) + "'");
}
/**
* Fail-safe substring method. Guarantees not to show the entire String.
*
* @param str the string to transform
* @param length number of characters to show from the start of the String
* @return the first <code>length</code> characters of the string, or half of the string if it is shorter,
* or empty string if the string is null or empty
*/
private static String safeSubstring(String str, int length) {
if (StringUtils.isEmpty(str)) {
return "";
} else if (str.length() < length) {
return str.substring(0, str.length() / 2) + "...";
} else {
return str.substring(0, length) + "...";
}
}
/**
* Formats the last login date from the given PlayerAuth.
*
* @param auth the auth object
* @return the last login as human readable date
*/
private static String formatLastLogin(PlayerAuth auth) {
long lastLogin = auth.getLastLogin();
if (lastLogin == 0) {
return "Never (0)";
} else {
LocalDateTime date = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastLogin), ZoneId.systemDefault());
return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(date);
}
}
}

View File

@ -0,0 +1,78 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.SpawnLoader;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.formatLocation;
/**
* Shows the spawn location that AuthMe is configured to use.
*/
class SpawnLocationViewer implements DebugSection {
@Inject
private SpawnLoader spawnLoader;
@Inject
private Settings settings;
@Inject
private BukkitService bukkitService;
@Override
public String getName() {
return "spawn";
}
@Override
public String getDescription() {
return "Shows the spawn location that AuthMe will use";
}
@Override
public void execute(CommandSender sender, List<String> arguments) {
if (arguments.isEmpty()) {
showGeneralInfo(sender);
} else if ("?".equals(arguments.get(0))) {
showHelp(sender);
} else {
showPlayerSpawn(sender, arguments.get(0));
}
}
private void showGeneralInfo(CommandSender sender) {
sender.sendMessage("Spawn priority: "
+ String.join(", ", settings.getProperty(RestrictionSettings.SPAWN_PRIORITY)));
sender.sendMessage("AuthMe spawn location: " + formatLocation(spawnLoader.getSpawn()));
sender.sendMessage("AuthMe first spawn location: " + formatLocation(spawnLoader.getFirstSpawn()));
sender.sendMessage("AuthMe (first)spawn are only used depending on the configured priority!");
sender.sendMessage("Use '/authme debug spawn ?' for further help");
}
private void showHelp(CommandSender sender) {
sender.sendMessage("Use /authme spawn and /authme firstspawn to teleport to the spawns.");
sender.sendMessage("/authme set(first)spawn sets the (first) spawn to your current location.");
sender.sendMessage("Use /authme debug spawn <player> to view where a player would be teleported to.");
sender.sendMessage("Read more at https://github.com/AuthMe/AuthMeReloaded/wiki/Spawn-Handling");
}
private void showPlayerSpawn(CommandSender sender, String playerName) {
Player player = bukkitService.getPlayerExact(playerName);
if (player == null) {
sender.sendMessage("Player '" + playerName + "' is not online!");
} else {
Location spawn = spawnLoader.getSpawnLocation(player);
sender.sendMessage("Player '" + playerName + "' has spawn location: " + formatLocation(spawn));
sender.sendMessage("Note: this check excludes the AuthMe firstspawn.");
}
}
}

View File

@ -0,0 +1,102 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.mail.SendMailSsl;
import fr.xephi.authme.util.StringUtils;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.bukkit.ChatColor;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
/**
* Sends out a test email.
*/
class TestEmailSender implements DebugSection {
@Inject
private DataSource dataSource;
@Inject
private SendMailSsl sendMailSsl;
@Inject
private Server server;
@Override
public String getName() {
return "mail";
}
@Override
public String getDescription() {
return "Sends out a test email";
}
@Override
public void execute(CommandSender sender, List<String> arguments) {
if (!sendMailSsl.hasAllInformation()) {
sender.sendMessage(ChatColor.RED + "You haven't set all required configurations in config.yml "
+ "for sending emails. Please check your config.yml");
return;
}
String email = getEmail(sender, arguments);
// getEmail() takes care of informing the sender of the error if email == null
if (email != null) {
boolean sendMail = sendTestEmail(email);
if (sendMail) {
sender.sendMessage("Test email sent to " + email + " with success");
} else {
sender.sendMessage(ChatColor.RED + "Failed to send test mail to " + email + "; please check your logs");
}
}
}
private String getEmail(CommandSender sender, List<String> arguments) {
if (arguments.isEmpty()) {
PlayerAuth auth = dataSource.getAuth(sender.getName());
if (auth == null) {
sender.sendMessage(ChatColor.RED + "Please provide an email address, "
+ "e.g. /authme debug mail test@example.com");
return null;
}
String email = auth.getEmail();
if (email == null || "your@email.com".equals(email)) {
sender.sendMessage(ChatColor.RED + "No email set for your account!"
+ " Please use /authme debug mail <email>");
return null;
}
return email;
} else {
String email = arguments.get(0);
if (StringUtils.isInsideString('@', email)) {
return email;
}
sender.sendMessage(ChatColor.RED + "Invalid email! Usage: /authme debug mail test@example.com");
return null;
}
}
private boolean sendTestEmail(String email) {
HtmlEmail htmlEmail;
try {
htmlEmail = sendMailSsl.initializeMail(email);
} catch (EmailException e) {
ConsoleLogger.logException("Failed to create email for sample email:", e);
return false;
}
htmlEmail.setSubject("AuthMe test email");
String message = "Hello there!<br />This is a sample email sent to you from a Minecraft server ("
+ server.getName() + ") via /authme debug mail. If you're seeing this, sending emails should be fine.";
return sendMailSsl.sendEmail(message, htmlEmail);
}
}

View File

@ -3,8 +3,7 @@ package fr.xephi.authme.command.executable.captcha;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.CaptchaManager;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService;
import org.bukkit.entity.Player;
@ -24,7 +23,7 @@ public class CaptchaCommand extends PlayerCommand {
private CommonService commonService;
@Inject
private LimboCache limboCache;
private LimboService limboService;
@Override
public void runCommand(Player player, List<String> arguments) {
@ -44,7 +43,7 @@ public class CaptchaCommand extends PlayerCommand {
if (isCorrectCode) {
commonService.send(player, MessageKey.CAPTCHA_SUCCESS);
commonService.send(player, MessageKey.LOGIN_MESSAGE);
limboCache.getPlayerData(player.getName()).getMessageTask().setMuted(false);
limboService.unmuteMessageTask(player);
} else {
String newCode = captchaManager.generateCode(player.getName());
commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode);

View File

@ -54,4 +54,9 @@ public class ChangePasswordCommand extends PlayerCommand {
protected String getAlternativeCommand() {
return "/authme password <playername> <password>";
}
@Override
public MessageKey getArgumentsMismatchMessage() {
return MessageKey.USAGE_CHANGE_PASSWORD;
}
}

View File

@ -32,4 +32,9 @@ public class AddEmailCommand extends PlayerCommand {
commonService.send(player, MessageKey.CONFIRM_EMAIL_MESSAGE);
}
}
@Override
public MessageKey getArgumentsMismatchMessage() {
return MessageKey.USAGE_ADD_EMAIL;
}
}

View File

@ -1,6 +1,7 @@
package fr.xephi.authme.command.executable.email;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management;
import org.bukkit.entity.Player;
@ -22,4 +23,9 @@ public class ChangeEmailCommand extends PlayerCommand {
management.performChangeEmail(player, playerMailOld, playerMailNew);
}
@Override
public MessageKey getArgumentsMismatchMessage() {
return MessageKey.USAGE_CHANGE_EMAIL;
}
}

View File

@ -0,0 +1,46 @@
package fr.xephi.authme.command.executable.email;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.PasswordRecoveryService;
import fr.xephi.authme.service.RecoveryCodeService;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Command for submitting email recovery code.
*/
public class ProcessCodeCommand extends PlayerCommand {
@Inject
private CommonService commonService;
@Inject
private RecoveryCodeService codeService;
@Inject
private PasswordRecoveryService recoveryService;
@Override
protected void runCommand(Player player, List<String> arguments) {
String name = player.getName();
String code = arguments.get(0);
if (codeService.hasTriesLeft(name)) {
if (codeService.isCodeValid(name, code)) {
commonService.send(player, MessageKey.RECOVERY_CODE_CORRECT);
recoveryService.addSuccessfulRecovery(player);
codeService.removeCode(name);
} else {
commonService.send(player, MessageKey.INCORRECT_RECOVERY_CODE,
Integer.toString(codeService.getTriesLeft(name)));
}
} else {
codeService.removeCode(name);
commonService.send(player, MessageKey.RECOVERY_TRIES_EXCEEDED);
}
}
}

View File

@ -5,28 +5,21 @@ import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.mail.SendMailSSL;
import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.PasswordRecoveryService;
import fr.xephi.authme.service.RecoveryCodeService;
import fr.xephi.authme.util.RandomStringUtils;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
/**
* Command for password recovery by email.
*/
public class RecoverEmailCommand extends PlayerCommand {
@Inject
private PasswordSecurity passwordSecurity;
@Inject
private CommonService commonService;
@ -37,17 +30,20 @@ public class RecoverEmailCommand extends PlayerCommand {
private PlayerCache playerCache;
@Inject
private SendMailSSL sendMailSsl;
private EmailService emailService;
@Inject
private PasswordRecoveryService recoveryService;
@Inject
private RecoveryCodeService recoveryCodeService;
@Override
public void runCommand(Player player, List<String> arguments) {
protected void runCommand(Player player, List<String> arguments) {
final String playerMail = arguments.get(0);
final String playerName = player.getName();
if (!sendMailSsl.hasAllInformation()) {
if (!emailService.hasAllInformation()) {
ConsoleLogger.warning("Mail API is not set");
commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS);
return;
@ -57,7 +53,7 @@ public class RecoverEmailCommand extends PlayerCommand {
return;
}
PlayerAuth auth = dataSource.getAuth(playerName); // TODO: Create method to get email only
PlayerAuth auth = dataSource.getAuth(playerName); // TODO #1127: Create method to get email only
if (auth == null) {
commonService.send(player, MessageKey.USAGE_REGISTER);
return;
@ -70,49 +66,16 @@ public class RecoverEmailCommand extends PlayerCommand {
}
if (recoveryCodeService.isRecoveryCodeNeeded()) {
// Process /email recovery addr@example.com
if (arguments.size() == 1) {
createAndSendRecoveryCode(player, email);
} else {
// Process /email recovery addr@example.com 12394
processRecoveryCode(player, arguments.get(1), email);
}
// Recovery code is needed; generate and send one
recoveryService.createAndSendRecoveryCode(player, email);
} else {
generateAndSendNewPassword(player, email);
// Code not needed, just send them a new password
recoveryService.generateAndSendNewPassword(player, email);
}
}
private void createAndSendRecoveryCode(Player player, String email) {
String recoveryCode = recoveryCodeService.generateCode(player.getName());
boolean couldSendMail = sendMailSsl.sendRecoveryCode(player.getName(), email, recoveryCode);
if (couldSendMail) {
commonService.send(player, MessageKey.RECOVERY_CODE_SENT);
} else {
commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
}
}
private void processRecoveryCode(Player player, String code, String email) {
final String name = player.getName();
if (recoveryCodeService.isCodeValid(name, code)) {
generateAndSendNewPassword(player, email);
recoveryCodeService.removeCode(name);
} else {
commonService.send(player, MessageKey.INCORRECT_RECOVERY_CODE);
}
}
private void generateAndSendNewPassword(Player player, String email) {
String name = player.getName();
String thePass = RandomStringUtils.generate(commonService.getProperty(RECOVERY_PASSWORD_LENGTH));
HashedPassword hashNew = passwordSecurity.computeHash(thePass, name);
dataSource.updatePassword(name, hashNew);
boolean couldSendMail = sendMailSsl.sendPasswordMail(name, email, thePass);
if (couldSendMail) {
commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
} else {
commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
}
@Override
public MessageKey getArgumentsMismatchMessage() {
return MessageKey.USAGE_RECOVER_EMAIL;
}
}

View File

@ -0,0 +1,55 @@
package fr.xephi.authme.command.executable.email;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.PasswordRecoveryService;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.service.ValidationService.ValidationResult;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Command for changing password following successful recovery.
*/
public class SetPasswordCommand extends PlayerCommand {
@Inject
private DataSource dataSource;
@Inject
private CommonService commonService;
@Inject
private PasswordRecoveryService recoveryService;
@Inject
private PasswordSecurity passwordSecurity;
@Inject
private ValidationService validationService;
@Override
protected void runCommand(Player player, List<String> arguments) {
if (recoveryService.canChangePassword(player)) {
String name = player.getName();
String password = arguments.get(0);
ValidationResult result = validationService.validatePassword(password, name);
if (!result.hasError()) {
HashedPassword hashedPassword = passwordSecurity.computeHash(password, name);
dataSource.updatePassword(name, hashedPassword);
ConsoleLogger.info("Player '" + name + "' has changed their password from recovery");
commonService.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS);
} else {
commonService.send(player, result.getMessageKey(), result.getArgs());
}
}
}
}

View File

@ -24,7 +24,7 @@ public class ShowEmailCommand extends PlayerCommand {
@Override
public void runCommand(Player player, List<String> arguments) {
PlayerAuth auth = playerCache.getAuth(player.getName());
if (auth.getEmail() != null && !"your@email.com".equalsIgnoreCase(auth.getEmail())) {
if (auth != null && auth.getEmail() != null && !"your@email.com".equalsIgnoreCase(auth.getEmail())) {
commonService.send(player, MessageKey.EMAIL_SHOW, auth.getEmail());
} else {
commonService.send(player, MessageKey.SHOW_NO_EMAIL);

View File

@ -2,12 +2,15 @@ package fr.xephi.authme.command.executable.register;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.mail.SendMailSSL;
import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
import fr.xephi.authme.process.register.RegistrationType;
import fr.xephi.authme.process.register.executors.RegistrationExecutorProvider;
import fr.xephi.authme.process.register.executors.EmailRegisterParams;
import fr.xephi.authme.process.register.executors.PasswordRegisterParams;
import fr.xephi.authme.process.register.executors.RegistrationMethod;
import fr.xephi.authme.process.register.executors.TwoFactorRegisterParams;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.ValidationService;
@ -37,20 +40,17 @@ public class RegisterCommand extends PlayerCommand {
private CommonService commonService;
@Inject
private SendMailSSL sendMailSsl;
private EmailService emailService;
@Inject
private ValidationService validationService;
@Inject
private RegistrationExecutorProvider registrationExecutorProvider;
@Override
public void runCommand(Player player, List<String> arguments) {
if (commonService.getProperty(SecuritySettings.PASSWORD_HASH) == HashAlgorithm.TWO_FACTOR) {
//for two factor auth we don't need to check the usage
management.performRegister(player,
registrationExecutorProvider.getTwoFactorRegisterExecutor(player));
management.performRegister(RegistrationMethod.TWO_FACTOR_REGISTRATION,
TwoFactorRegisterParams.of(player));
return;
} else if (arguments.size() < 1) {
commonService.send(player, MessageKey.USAGE_REGISTER);
@ -82,8 +82,8 @@ public class RegisterCommand extends PlayerCommand {
final String password = arguments.get(0);
final String email = getEmailIfAvailable(arguments);
management.performRegister(
player, registrationExecutorProvider.getPasswordRegisterExecutor(player, password, email));
management.performRegister(RegistrationMethod.PASSWORD_REGISTRATION,
PasswordRegisterParams.of(player, password, email));
}
}
@ -127,7 +127,7 @@ public class RegisterCommand extends PlayerCommand {
}
private void handleEmailRegistration(Player player, List<String> arguments) {
if (!sendMailSsl.hasAllInformation()) {
if (!emailService.hasAllInformation()) {
commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS);
ConsoleLogger.warning("Cannot register player '" + player.getName() + "': no email or password is set "
+ "to send emails from. Please adjust your config at " + EmailSettings.MAIL_ACCOUNT.getPath());
@ -138,7 +138,8 @@ public class RegisterCommand extends PlayerCommand {
if (!validationService.validateEmail(email)) {
commonService.send(player, MessageKey.INVALID_EMAIL);
} else if (isSecondArgValidForEmailRegistration(player, arguments)) {
management.performRegister(player, registrationExecutorProvider.getEmailRegisterExecutor(player, email));
management.performRegister(RegistrationMethod.EMAIL_REGISTRATION,
EmailRegisterParams.of(player, email));
}
}

View File

@ -38,4 +38,14 @@ public class UnregisterCommand extends PlayerCommand {
// Unregister the player
management.performUnregister(player, playerPass);
}
@Override
public MessageKey getArgumentsMismatchMessage() {
return MessageKey.USAGE_UNREGISTER;
}
@Override
protected String getAlternativeCommand() {
return "/authme unregister <player>";
}
}

View File

@ -81,8 +81,8 @@ public class HelpMessagesService implements Reloadable {
public String getMessage(DefaultPermission defaultPermission) {
// e.g. {default_permissions_path}.opOnly for DefaultPermission.OP_ONLY
String path = DEFAULT_PERMISSIONS_PATH +
CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, defaultPermission.name());
String path = DEFAULT_PERMISSIONS_PATH
+ CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, defaultPermission.name());
return messageFileHandler.getMessage(path);
}

View File

@ -1,19 +1,22 @@
package fr.xephi.authme.data;
import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.util.expiring.TimedCounter;
import javax.inject.Inject;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* Manager for the handling of captchas.
*/
public class CaptchaManager implements SettingsDependent {
public class CaptchaManager implements SettingsDependent, HasCleanup {
private final ConcurrentHashMap<String, Integer> playerCounts;
private final TimedCounter<String> playerCounts;
private final ConcurrentHashMap<String, String> captchaCodes;
private boolean isEnabled;
@ -22,8 +25,9 @@ public class CaptchaManager implements SettingsDependent {
@Inject
CaptchaManager(Settings settings) {
this.playerCounts = new ConcurrentHashMap<>();
this.captchaCodes = new ConcurrentHashMap<>();
long countTimeout = settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET);
this.playerCounts = new TimedCounter<>(countTimeout, TimeUnit.MINUTES);
reload(settings);
}
@ -35,12 +39,7 @@ public class CaptchaManager implements SettingsDependent {
public void increaseCount(String name) {
if (isEnabled) {
String playerLower = name.toLowerCase();
Integer currentCount = playerCounts.get(playerLower);
if (currentCount == null) {
playerCounts.put(playerLower, 1);
} else {
playerCounts.put(playerLower, currentCount + 1);
}
playerCounts.increment(playerLower);
}
}
@ -51,21 +50,7 @@ public class CaptchaManager implements SettingsDependent {
* @return true if the player has to solve a captcha, false otherwise
*/
public boolean isCaptchaRequired(String name) {
if (isEnabled) {
Integer count = playerCounts.get(name.toLowerCase());
return count != null && count >= threshold;
}
return false;
}
/**
* Returns the stored captcha code for the player.
*
* @param name the player's name
* @return the code the player is required to enter, or null if none registered
*/
public String getCaptchaCode(String name) {
return captchaCodes.get(name.toLowerCase());
return isEnabled && playerCounts.get(name.toLowerCase()) >= threshold;
}
/**
@ -75,7 +60,7 @@ public class CaptchaManager implements SettingsDependent {
* @return the code the player is required to enter
*/
public String getCaptchaCodeOrGenerateNew(String name) {
String code = getCaptchaCode(name);
String code = captchaCodes.get(name.toLowerCase());
return code == null ? generateCode(name) : code;
}
@ -127,6 +112,13 @@ public class CaptchaManager implements SettingsDependent {
this.isEnabled = settings.getProperty(SecuritySettings.USE_CAPTCHA);
this.threshold = settings.getProperty(SecuritySettings.MAX_LOGIN_TRIES_BEFORE_CAPTCHA);
this.captchaLength = settings.getProperty(SecuritySettings.CAPTCHA_LENGTH);
long countTimeout = settings.getProperty(SecuritySettings.CAPTCHA_COUNT_MINUTES_BEFORE_RESET);
playerCounts.setExpiration(countTimeout, TimeUnit.MINUTES);
}
@Override
public void performCleanup() {
playerCounts.removeExpiredEntries();
}
}

View File

@ -5,13 +5,10 @@ import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.util.expiring.ExpiringSet;
import javax.inject.Inject;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE;
import java.util.concurrent.TimeUnit;
/**
* Manages sessions, allowing players to be automatically logged in if they join again
@ -19,15 +16,14 @@ import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE;
*/
public class SessionManager implements SettingsDependent, HasCleanup {
// Player -> expiration of session in milliseconds
private final Map<String, Long> sessions = new ConcurrentHashMap<>();
private final ExpiringSet<String> sessions;
private boolean enabled;
private int timeoutInMinutes;
@Inject
SessionManager(Settings settings) {
reload(settings);
long timeout = settings.getProperty(PluginSettings.SESSIONS_TIMEOUT);
sessions = new ExpiringSet<>(timeout, TimeUnit.MINUTES);
enabled = timeout > 0 && settings.getProperty(PluginSettings.SESSIONS_ENABLED);
}
/**
@ -37,13 +33,7 @@ public class SessionManager implements SettingsDependent, HasCleanup {
* @return True if a session is found.
*/
public boolean hasSession(String name) {
if (enabled) {
Long timeout = sessions.get(name.toLowerCase());
if (timeout != null) {
return System.currentTimeMillis() <= timeout;
}
}
return false;
return enabled && sessions.contains(name.toLowerCase());
}
/**
@ -53,8 +43,7 @@ public class SessionManager implements SettingsDependent, HasCleanup {
*/
public void addSession(String name) {
if (enabled) {
long timeout = System.currentTimeMillis() + timeoutInMinutes * MILLIS_PER_MINUTE;
sessions.put(name.toLowerCase(), timeout);
sessions.add(name.toLowerCase());
}
}
@ -64,12 +53,13 @@ public class SessionManager implements SettingsDependent, HasCleanup {
* @param name The name of the player.
*/
public void removeSession(String name) {
this.sessions.remove(name.toLowerCase());
sessions.remove(name.toLowerCase());
}
@Override
public void reload(Settings settings) {
timeoutInMinutes = settings.getProperty(PluginSettings.SESSIONS_TIMEOUT);
long timeoutInMinutes = settings.getProperty(PluginSettings.SESSIONS_TIMEOUT);
sessions.setExpiration(timeoutInMinutes, TimeUnit.MINUTES);
boolean oldEnabled = enabled;
enabled = timeoutInMinutes > 0 && settings.getProperty(PluginSettings.SESSIONS_ENABLED);
@ -82,16 +72,8 @@ public class SessionManager implements SettingsDependent, HasCleanup {
@Override
public void performCleanup() {
if (!enabled) {
return;
}
final long currentTime = System.currentTimeMillis();
Iterator<Map.Entry<String, Long>> iterator = sessions.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Long> entry = iterator.next();
if (entry.getValue() < currentTime) {
iterator.remove();
}
if (enabled) {
sessions.removeExpiredEntries();
}
}
}

View File

@ -1,21 +1,21 @@
package fr.xephi.authme.data;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.util.PlayerUtils;
import fr.xephi.authme.util.expiring.TimedCounter;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import static fr.xephi.authme.settings.properties.SecuritySettings.TEMPBAN_MINUTES_BEFORE_RESET;
import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE;
@ -25,7 +25,7 @@ import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE;
*/
public class TempbanManager implements SettingsDependent, HasCleanup {
private final Map<String, Map<String, TimedCounter>> ipLoginFailureCounts;
private final Map<String, TimedCounter<String>> ipLoginFailureCounts;
private final BukkitService bukkitService;
private final Messages messages;
@ -50,18 +50,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
*/
public void increaseCount(String address, String name) {
if (isEnabled) {
Map<String, TimedCounter> countsByName = ipLoginFailureCounts.get(address);
if (countsByName == null) {
countsByName = new ConcurrentHashMap<>();
ipLoginFailureCounts.put(address, countsByName);
}
TimedCounter counter = countsByName.get(name);
if (counter == null) {
countsByName.put(name, new TimedCounter(1));
} else {
counter.increment(resetThreshold);
}
TimedCounter<String> countsByName = ipLoginFailureCounts.computeIfAbsent(
address, k -> new TimedCounter<>(resetThreshold, TimeUnit.MINUTES));
countsByName.increment(name);
}
}
@ -73,9 +64,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
*/
public void resetCount(String address, String name) {
if (isEnabled) {
Map<String, TimedCounter> map = ipLoginFailureCounts.get(address);
if (map != null) {
map.remove(name);
TimedCounter<String> counter = ipLoginFailureCounts.get(address);
if (counter != null) {
counter.remove(name);
}
}
}
@ -88,13 +79,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
*/
public boolean shouldTempban(String address) {
if (isEnabled) {
Map<String, TimedCounter> countsByName = ipLoginFailureCounts.get(address);
TimedCounter<String> countsByName = ipLoginFailureCounts.get(address);
if (countsByName != null) {
int total = 0;
for (TimedCounter counter : countsByName.values()) {
total += counter.getCount(resetThreshold);
}
return total >= threshold;
return countsByName.total() >= threshold;
}
}
return false;
@ -137,56 +124,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
@Override
public void performCleanup() {
for (Map<String, TimedCounter> countsByIp : ipLoginFailureCounts.values()) {
Iterator<TimedCounter> it = countsByIp.values().iterator();
while (it.hasNext()) {
TimedCounter counter = it.next();
if (counter.getCount(resetThreshold) == 0) {
it.remove();
}
}
}
}
/**
* Counter with an associated timestamp, keeping track of when the last entry has been added.
*/
@VisibleForTesting
static final class TimedCounter {
private int counter;
private long lastIncrementTimestamp = System.currentTimeMillis();
/**
* Constructor.
*
* @param start the initial value to set the counter to
*/
TimedCounter(int start) {
this.counter = start;
}
/**
* Returns the count, taking into account the last entry timestamp.
*
* @param threshold the threshold in milliseconds until when to consider a counter
* @return the counter's value, or {@code 0} if it was last incremented longer ago than the threshold
*/
int getCount(long threshold) {
if (System.currentTimeMillis() - lastIncrementTimestamp > threshold) {
return 0;
}
return counter;
}
/**
* Increments the counter, taking into account the last entry timestamp.
*
* @param threshold in milliseconds, the time span until which to consider the existing number
*/
void increment(long threshold) {
counter = getCount(threshold) + 1;
lastIncrementTimestamp = System.currentTimeMillis();
for (TimedCounter<String> countsByIp : ipLoginFailureCounts.values()) {
countsByIp.removeExpiredEntries();
}
ipLoginFailureCounts.entrySet().removeIf(e -> e.getValue().isEmpty());
}
}

View File

@ -1,214 +0,0 @@
package fr.xephi.authme.data.backup;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.settings.SpawnLoader;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.util.FileUtils;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
/**
* Class used to store player's data (OP, flying, speed, position) to disk.
*/
public class LimboPlayerStorage {
private final Gson gson;
private final File cacheDir;
private PermissionsManager permissionsManager;
private SpawnLoader spawnLoader;
private BukkitService bukkitService;
@Inject
LimboPlayerStorage(@DataFolder File dataFolder, PermissionsManager permsMan,
SpawnLoader spawnLoader, BukkitService bukkitService) {
this.permissionsManager = permsMan;
this.spawnLoader = spawnLoader;
this.bukkitService = bukkitService;
cacheDir = new File(dataFolder, "playerdata");
if (!cacheDir.exists() && !cacheDir.isDirectory() && !cacheDir.mkdir()) {
ConsoleLogger.warning("Failed to create userdata directory.");
}
gson = new GsonBuilder()
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer())
.setPrettyPrinting()
.create();
}
/**
* Read and construct new PlayerData from existing player data.
*
* @param player player to read
*
* @return PlayerData object if the data is exist, null otherwise.
*/
public LimboPlayer readData(Player player) {
String id = PlayerUtils.getUUIDorName(player);
File file = new File(cacheDir, id + File.separator + "data.json");
if (!file.exists()) {
return null;
}
try {
String str = Files.toString(file, StandardCharsets.UTF_8);
return gson.fromJson(str, LimboPlayer.class);
} catch (IOException e) {
ConsoleLogger.logException("Could not read player data on disk for '" + player.getName() + "'", e);
return null;
}
}
/**
* Save player data (OP, flying, location, etc) to disk.
*
* @param player player to save
*/
public void saveData(Player player) {
String id = PlayerUtils.getUUIDorName(player);
Location location = spawnLoader.getPlayerLocationOrSpawn(player);
String group = "";
if (permissionsManager.hasGroupSupport()) {
group = permissionsManager.getPrimaryGroup(player);
}
boolean operator = player.isOp();
boolean canFly = player.getAllowFlight();
float walkSpeed = player.getWalkSpeed();
float flySpeed = player.getFlySpeed();
LimboPlayer limboPlayer = new LimboPlayer(location, operator, group, canFly, walkSpeed, flySpeed);
try {
File file = new File(cacheDir, id + File.separator + "data.json");
Files.createParentDirs(file);
Files.touch(file);
Files.write(gson.toJson(limboPlayer), file, StandardCharsets.UTF_8);
} catch (IOException e) {
ConsoleLogger.logException("Failed to write " + player.getName() + " data.", e);
}
}
/**
* Remove player data, this will delete
* "playerdata/&lt;uuid or name&gt;/" folder from disk.
*
* @param player player to remove
*/
public void removeData(Player player) {
String id = PlayerUtils.getUUIDorName(player);
File file = new File(cacheDir, id);
if (file.exists()) {
FileUtils.purgeDirectory(file);
if (!file.delete()) {
ConsoleLogger.warning("Failed to remove " + player.getName() + " cache.");
}
}
}
/**
* Use to check is player data is exist.
*
* @param player player to check
*
* @return true if data exist, false otherwise.
*/
public boolean hasData(Player player) {
String id = PlayerUtils.getUUIDorName(player);
File file = new File(cacheDir, id + File.separator + "data.json");
return file.exists();
}
private class LimboPlayerDeserializer implements JsonDeserializer<LimboPlayer> {
@Override
public LimboPlayer deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext context) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject == null) {
return null;
}
Location loc = null;
String group = "";
boolean operator = false;
boolean canFly = false;
float walkSpeed = 0.2f;
float flySpeed = 0.2f;
JsonElement e;
if ((e = jsonObject.getAsJsonObject("location")) != null) {
JsonObject obj = e.getAsJsonObject();
World world = bukkitService.getWorld(obj.get("world").getAsString());
if (world != null) {
double x = obj.get("x").getAsDouble();
double y = obj.get("y").getAsDouble();
double z = obj.get("z").getAsDouble();
float yaw = obj.get("yaw").getAsFloat();
float pitch = obj.get("pitch").getAsFloat();
loc = new Location(world, x, y, z, yaw, pitch);
}
}
if ((e = jsonObject.get("group")) != null) {
group = e.getAsString();
}
if ((e = jsonObject.get("operator")) != null) {
operator = e.getAsBoolean();
}
if ((e = jsonObject.get("can-fly")) != null) {
canFly = e.getAsBoolean();
}
if ((e = jsonObject.get("walk-speed")) != null) {
walkSpeed = e.getAsFloat();
}
if ((e = jsonObject.get("fly-speed")) != null) {
flySpeed = e.getAsFloat();
}
return new LimboPlayer(loc, operator, group, canFly, walkSpeed, flySpeed);
}
}
private class LimboPlayerSerializer implements JsonSerializer<LimboPlayer> {
@Override
public JsonElement serialize(LimboPlayer limboPlayer, Type type,
JsonSerializationContext context) {
JsonObject obj = new JsonObject();
obj.addProperty("group", limboPlayer.getGroup());
Location loc = limboPlayer.getLocation();
JsonObject obj2 = new JsonObject();
obj2.addProperty("world", loc.getWorld().getName());
obj2.addProperty("x", loc.getX());
obj2.addProperty("y", loc.getY());
obj2.addProperty("z", loc.getZ());
obj2.addProperty("yaw", loc.getYaw());
obj2.addProperty("pitch", loc.getPitch());
obj.add("location", obj2);
obj.addProperty("operator", limboPlayer.isOperator());
obj.addProperty("can-fly", limboPlayer.isCanFly());
obj.addProperty("walk-speed", limboPlayer.getWalkSpeed());
obj.addProperty("fly-speed", limboPlayer.getFlySpeed());
return obj;
}
}
}

View File

@ -0,0 +1,43 @@
package fr.xephi.authme.data.limbo;
import org.bukkit.entity.Player;
import java.util.function.Function;
/**
* Possible types to restore the "allow flight" property
* from LimboPlayer to Bukkit Player.
*/
public enum AllowFlightRestoreType {
/** Set value from LimboPlayer to Player. */
RESTORE(LimboPlayer::isCanFly),
/** Always set flight enabled to true. */
ENABLE(l -> true),
/** Always set flight enabled to false. */
DISABLE(l -> false);
private final Function<LimboPlayer, Boolean> valueGetter;
/**
* Constructor.
*
* @param valueGetter function with which the value to set on the player can be retrieved
*/
AllowFlightRestoreType(Function<LimboPlayer, Boolean> valueGetter) {
this.valueGetter = valueGetter;
}
/**
* Restores the "allow flight" property from the LimboPlayer to the Player.
* This method behaves differently for each restoration type.
*
* @param player the player to modify
* @param limbo the limbo player to read from
*/
public void restoreAllowFlight(Player player, LimboPlayer limbo) {
player.setAllowFlight(valueGetter.apply(limbo));
}
}

View File

@ -1,164 +0,0 @@
package fr.xephi.authme.data.limbo;
import fr.xephi.authme.data.backup.LimboPlayerStorage;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.SpawnLoader;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Manages all {@link LimboPlayer} instances.
*/
public class LimboCache {
private final Map<String, LimboPlayer> cache = new ConcurrentHashMap<>();
private LimboPlayerStorage limboPlayerStorage;
private Settings settings;
private PermissionsManager permissionsManager;
private SpawnLoader spawnLoader;
@Inject
LimboCache(Settings settings, PermissionsManager permissionsManager,
SpawnLoader spawnLoader, LimboPlayerStorage limboPlayerStorage) {
this.settings = settings;
this.permissionsManager = permissionsManager;
this.spawnLoader = spawnLoader;
this.limboPlayerStorage = limboPlayerStorage;
}
/**
* Load player data if exist, otherwise current player's data will be stored.
*
* @param player Player instance to add.
*/
public void addPlayerData(Player player) {
String name = player.getName().toLowerCase();
Location location = spawnLoader.getPlayerLocationOrSpawn(player);
boolean operator = player.isOp();
boolean flyEnabled = player.getAllowFlight();
float walkSpeed = player.getWalkSpeed();
float flySpeed = player.getFlySpeed();
String playerGroup = "";
if (permissionsManager.hasGroupSupport()) {
playerGroup = permissionsManager.getPrimaryGroup(player);
}
if (limboPlayerStorage.hasData(player)) {
LimboPlayer cache = limboPlayerStorage.readData(player);
if (cache != null) {
location = cache.getLocation();
playerGroup = cache.getGroup();
operator = cache.isOperator();
flyEnabled = cache.isCanFly();
walkSpeed = cache.getWalkSpeed();
flySpeed = cache.getFlySpeed();
}
} else {
limboPlayerStorage.saveData(player);
}
cache.put(name, new LimboPlayer(location, operator, playerGroup, flyEnabled, walkSpeed, flySpeed));
}
/**
* Restore player's data to player if exist.
*
* @param player Player instance to restore
*/
public void restoreData(Player player) {
String lowerName = player.getName().toLowerCase();
if (cache.containsKey(lowerName)) {
LimboPlayer data = cache.get(lowerName);
player.setOp(data.isOperator());
player.setAllowFlight(data.isCanFly());
float walkSpeed = data.getWalkSpeed();
float flySpeed = data.getFlySpeed();
// Reset the speed value if it was 0
if(walkSpeed < 0.01f) {
walkSpeed = 0.2f;
}
if(flySpeed < 0.01f) {
flySpeed = 0.2f;
}
player.setWalkSpeed(walkSpeed);
player.setFlySpeed(flySpeed);
restoreGroup(player, data.getGroup());
data.clearTasks();
}
}
/**
* Remove PlayerData from cache and disk.
*
* @param player Player player to remove.
*/
public void deletePlayerData(Player player) {
removeFromCache(player);
limboPlayerStorage.removeData(player);
}
/**
* Remove PlayerData from cache.
*
* @param player player to remove.
*/
public void removeFromCache(Player player) {
String name = player.getName().toLowerCase();
LimboPlayer cachedPlayer = cache.remove(name);
if (cachedPlayer != null) {
cachedPlayer.clearTasks();
}
}
/**
* Method getPlayerData.
*
* @param name String
*
* @return PlayerData
*/
public LimboPlayer getPlayerData(String name) {
checkNotNull(name);
return cache.get(name.toLowerCase());
}
/**
* Method hasPlayerData.
*
* @param name String
*
* @return boolean
*/
public boolean hasPlayerData(String name) {
checkNotNull(name);
return cache.containsKey(name.toLowerCase());
}
/**
* Method updatePlayerData.
*
* @param player Player
*/
public void updatePlayerData(Player player) {
checkNotNull(player);
removeFromCache(player);
addPlayerData(player);
}
private void restoreGroup(Player player, String group) {
if (!StringUtils.isEmpty(group) && permissionsManager.hasGroupSupport()
&& settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) {
permissionsManager.setGroup(player, group);
}
}
}

View File

@ -10,6 +10,9 @@ import org.bukkit.scheduler.BukkitTask;
*/
public class LimboPlayer {
public static final float DEFAULT_WALK_SPEED = 0.2f;
public static final float DEFAULT_FLY_SPEED = 0.1f;
private final boolean canFly;
private final boolean operator;
private final String group;
@ -115,13 +118,7 @@ public class LimboPlayer {
* Clears all tasks associated to the player.
*/
public void clearTasks() {
if (messageTask != null) {
messageTask.cancel();
}
messageTask = null;
if (timeoutTask != null) {
timeoutTask.cancel();
}
timeoutTask = null;
setMessageTask(null);
setTimeoutTask(null);
}
}

View File

@ -0,0 +1,97 @@
package fr.xephi.authme.data.limbo;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.task.MessageTask;
import fr.xephi.authme.task.TimeoutTask;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask;
import javax.inject.Inject;
import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND;
/**
* Registers tasks associated with a LimboPlayer.
*/
class LimboPlayerTaskManager {
@Inject
private Messages messages;
@Inject
private Settings settings;
@Inject
private BukkitService bukkitService;
@Inject
private PlayerCache playerCache;
LimboPlayerTaskManager() {
}
/**
* Registers a {@link MessageTask} for the given player name.
*
* @param player the player
* @param limbo the associated limbo player of the player
* @param isRegistered whether the player is registered or not
* (false shows "please register", true shows "please log in")
*/
void registerMessageTask(Player player, LimboPlayer limbo, boolean isRegistered) {
int interval = settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL);
MessageKey key = getMessageKey(isRegistered);
if (interval > 0) {
MessageTask messageTask = new MessageTask(player, messages.retrieve(key));
bukkitService.runTaskTimer(messageTask, 2 * TICKS_PER_SECOND, interval * TICKS_PER_SECOND);
limbo.setMessageTask(messageTask);
}
}
/**
* Registers a {@link TimeoutTask} for the given player according to the configuration.
*
* @param player the player to register a timeout task for
* @param limbo the associated limbo player
*/
void registerTimeoutTask(Player player, LimboPlayer limbo) {
final int timeout = settings.getProperty(RestrictionSettings.TIMEOUT) * TICKS_PER_SECOND;
if (timeout > 0) {
String message = messages.retrieveSingle(MessageKey.LOGIN_TIMEOUT_ERROR);
BukkitTask task = bukkitService.runTaskLater(new TimeoutTask(player, message, playerCache), timeout);
limbo.setTimeoutTask(task);
}
}
/**
* Null-safe method to set the muted flag on a message task.
*
* @param task the task to modify (or null)
* @param isMuted the value to set if task is not null
*/
static void setMuted(MessageTask task, boolean isMuted) {
if (task != null) {
task.setMuted(isMuted);
}
}
/**
* Returns the appropriate message key according to the registration status and settings.
*
* @param isRegistered whether or not the username is registered
* @return the message key to display to the user
*/
private static MessageKey getMessageKey(boolean isRegistered) {
if (isRegistered) {
return MessageKey.LOGIN_MESSAGE;
} else {
return MessageKey.REGISTER_MESSAGE;
}
}
}

View File

@ -0,0 +1,170 @@
package fr.xephi.authme.data.limbo;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.persistence.LimboPersistence;
import fr.xephi.authme.settings.Settings;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_ALLOW_FLIGHT;
import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_FLY_SPEED;
import static fr.xephi.authme.settings.properties.LimboSettings.RESTORE_WALK_SPEED;
/**
* Service for managing players that are in "limbo," a temporary state players are
* put in which have joined but not yet logged in yet.
*/
public class LimboService {
private final Map<String, LimboPlayer> entries = new ConcurrentHashMap<>();
@Inject
private Settings settings;
@Inject
private LimboPlayerTaskManager taskManager;
@Inject
private LimboServiceHelper helper;
@Inject
private LimboPersistence persistence;
LimboService() {
}
/**
* Creates a LimboPlayer for the given player and revokes all "limbo data" from the player.
*
* @param player the player to process
* @param isRegistered whether or not the player is registered
*/
public void createLimboPlayer(Player player, boolean isRegistered) {
final String name = player.getName().toLowerCase();
LimboPlayer limboFromDisk = persistence.getLimboPlayer(player);
if (limboFromDisk != null) {
ConsoleLogger.debug("LimboPlayer for `{0}` already exists on disk", name);
}
LimboPlayer existingLimbo = entries.remove(name);
if (existingLimbo != null) {
existingLimbo.clearTasks();
ConsoleLogger.debug("LimboPlayer for `{0}` already present in memory", name);
}
LimboPlayer limboPlayer = helper.merge(existingLimbo, limboFromDisk);
limboPlayer = helper.merge(helper.createLimboPlayer(player, isRegistered), limboPlayer);
taskManager.registerMessageTask(player, limboPlayer, isRegistered);
taskManager.registerTimeoutTask(player, limboPlayer);
helper.revokeLimboStates(player);
entries.put(name, limboPlayer);
persistence.saveLimboPlayer(player, limboPlayer);
}
/**
* Returns the limbo player for the given name, or null otherwise.
*
* @param name the name to retrieve the data for
* @return the associated limbo player, or null if none available
*/
public LimboPlayer getLimboPlayer(String name) {
return entries.get(name.toLowerCase());
}
/**
* Returns whether there is a limbo player for the given name.
*
* @param name the name to check
* @return true if present, false otherwise
*/
public boolean hasLimboPlayer(String name) {
return entries.containsKey(name.toLowerCase());
}
/**
* Restores the limbo data and subsequently deletes the entry.
* <p>
* Note that teleportation on the player is performed by {@link fr.xephi.authme.service.TeleportationService} and
* changing the permission group is handled by {@link fr.xephi.authme.permission.AuthGroupHandler}.
*
* @param player the player whose data should be restored
*/
public void restoreData(Player player) {
String lowerName = player.getName().toLowerCase();
LimboPlayer limbo = entries.remove(lowerName);
if (limbo == null) {
ConsoleLogger.debug("No LimboPlayer found for `{0}` - cannot restore", lowerName);
} else {
player.setOp(limbo.isOperator());
settings.getProperty(RESTORE_ALLOW_FLIGHT).restoreAllowFlight(player, limbo);
settings.getProperty(RESTORE_FLY_SPEED).restoreFlySpeed(player, limbo);
settings.getProperty(RESTORE_WALK_SPEED).restoreWalkSpeed(player, limbo);
limbo.clearTasks();
ConsoleLogger.debug("Restored LimboPlayer stats for `{0}`", lowerName);
persistence.removeLimboPlayer(player);
}
}
/**
* Creates new tasks for the given player and cancels the old ones for a newly registered player.
* This resets his time to log in (TimeoutTask) and updates the message he is shown (MessageTask).
*
* @param player the player to reset the tasks for
*/
public void replaceTasksAfterRegistration(Player player) {
getLimboOrLogError(player, "reset tasks")
.ifPresent(limbo -> {
taskManager.registerTimeoutTask(player, limbo);
taskManager.registerMessageTask(player, limbo, true);
});
}
/**
* Resets the message task associated with the player's LimboPlayer.
*
* @param player the player to set a new message task for
* @param isRegistered whether or not the player is registered
*/
public void resetMessageTask(Player player, boolean isRegistered) {
getLimboOrLogError(player, "reset message task")
.ifPresent(limbo -> taskManager.registerMessageTask(player, limbo, isRegistered));
}
/**
* @param player the player whose message task should be muted
*/
public void muteMessageTask(Player player) {
getLimboOrLogError(player, "mute message task")
.ifPresent(limbo -> LimboPlayerTaskManager.setMuted(limbo.getMessageTask(), true));
}
/**
* @param player the player whose message task should be unmuted
*/
public void unmuteMessageTask(Player player) {
getLimboOrLogError(player, "unmute message task")
.ifPresent(limbo -> LimboPlayerTaskManager.setMuted(limbo.getMessageTask(), false));
}
/**
* Returns the limbo player for the given player or logs an error.
*
* @param player the player to retrieve the limbo player for
* @param context the action for which the limbo player is being retrieved (for logging)
* @return Optional with the limbo player
*/
private Optional<LimboPlayer> getLimboOrLogError(Player player, String context) {
LimboPlayer limbo = entries.get(player.getName().toLowerCase());
if (limbo == null) {
ConsoleLogger.debug("No LimboPlayer found for `{0}`. Action: {1}", player.getName(), context);
}
return Optional.ofNullable(limbo);
}
}

View File

@ -0,0 +1,106 @@
package fr.xephi.authme.data.limbo;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.SpawnLoader;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import javax.inject.Inject;
/**
* Helper class for the LimboService.
*/
class LimboServiceHelper {
@Inject
private SpawnLoader spawnLoader;
@Inject
private PermissionsManager permissionsManager;
@Inject
private Settings settings;
/**
* Creates a LimboPlayer with the given player's details.
*
* @param player the player to process
* @param isRegistered whether the player is registered
* @return limbo player with the player's data
*/
LimboPlayer createLimboPlayer(Player player, boolean isRegistered) {
Location location = spawnLoader.getPlayerLocationOrSpawn(player);
// For safety reasons an unregistered player should not have OP status after registration
boolean isOperator = isRegistered && player.isOp();
boolean flyEnabled = player.getAllowFlight();
float walkSpeed = player.getWalkSpeed();
float flySpeed = player.getFlySpeed();
String playerGroup = permissionsManager.hasGroupSupport()
? permissionsManager.getPrimaryGroup(player) : "";
ConsoleLogger.debug("Player `{0}` has primary group `{1}`", player.getName(), playerGroup);
return new LimboPlayer(location, isOperator, playerGroup, flyEnabled, walkSpeed, flySpeed);
}
/**
* Removes the data that is saved in a LimboPlayer from the player.
* <p>
* Note that teleportation on the player is performed by {@link fr.xephi.authme.service.TeleportationService} and
* changing the permission group is handled by {@link fr.xephi.authme.permission.AuthGroupHandler}.
*
* @param player the player to set defaults to
*/
void revokeLimboStates(Player player) {
player.setOp(false);
player.setAllowFlight(false);
if (!settings.getProperty(RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT)) {
player.setFlySpeed(0.0f);
player.setWalkSpeed(0.0f);
}
}
/**
* Merges two existing LimboPlayer instances of a player. Merging is done the following way:
* <ul>
* <li><code>isOperator, allowFlight</code>: true if either limbo has true</li>
* <li><code>flySpeed, walkSpeed</code>: maximum value of either limbo player</li>
* <li><code>group, location</code>: from old limbo if not empty/null, otherwise from new limbo</li>
* </ul>
*
* @param newLimbo the new limbo player
* @param oldLimbo the old limbo player
* @return merged limbo player if both arguments are not null, otherwise the first non-null argument
*/
LimboPlayer merge(LimboPlayer newLimbo, LimboPlayer oldLimbo) {
if (newLimbo == null) {
return oldLimbo;
} else if (oldLimbo == null) {
return newLimbo;
}
boolean isOperator = newLimbo.isOperator() || oldLimbo.isOperator();
boolean canFly = newLimbo.isCanFly() || oldLimbo.isCanFly();
float flySpeed = Math.max(newLimbo.getFlySpeed(), oldLimbo.getFlySpeed());
float walkSpeed = Math.max(newLimbo.getWalkSpeed(), oldLimbo.getWalkSpeed());
String group = firstNotEmpty(newLimbo.getGroup(), oldLimbo.getGroup());
Location location = firstNotNull(oldLimbo.getLocation(), newLimbo.getLocation());
return new LimboPlayer(location, isOperator, group, canFly, walkSpeed, flySpeed);
}
private static String firstNotEmpty(String newGroup, String oldGroup) {
ConsoleLogger.debug("Limbo merge: new and old perm groups are `{0}` and `{1}`", newGroup, oldGroup);
if ("".equals(oldGroup)) {
return newGroup;
}
return oldGroup;
}
private static Location firstNotNull(Location first, Location second) {
return first == null ? second : first;
}
}

View File

@ -0,0 +1,81 @@
package fr.xephi.authme.data.limbo;
import org.bukkit.entity.Player;
/**
* Possible types to restore the walk and fly speed from LimboPlayer
* back to Bukkit Player.
*/
public enum WalkFlySpeedRestoreType {
/** Restores from LimboPlayer to Player. */
RESTORE {
@Override
public void restoreFlySpeed(Player player, LimboPlayer limbo) {
player.setFlySpeed(limbo.getFlySpeed());
}
@Override
public void restoreWalkSpeed(Player player, LimboPlayer limbo) {
player.setWalkSpeed(limbo.getWalkSpeed());
}
},
/** Restores from LimboPlayer, using the default speed if the speed on LimboPlayer is 0. */
RESTORE_NO_ZERO {
@Override
public void restoreFlySpeed(Player player, LimboPlayer limbo) {
float limboFlySpeed = limbo.getFlySpeed();
player.setFlySpeed(limboFlySpeed > 0.01f ? limboFlySpeed : LimboPlayer.DEFAULT_FLY_SPEED);
}
@Override
public void restoreWalkSpeed(Player player, LimboPlayer limbo) {
float limboWalkSpeed = limbo.getWalkSpeed();
player.setWalkSpeed(limboWalkSpeed > 0.01f ? limboWalkSpeed : LimboPlayer.DEFAULT_WALK_SPEED);
}
},
/** Uses the max speed of Player (current speed) and the LimboPlayer. */
MAX_RESTORE {
@Override
public void restoreFlySpeed(Player player, LimboPlayer limbo) {
player.setFlySpeed(Math.max(player.getFlySpeed(), limbo.getFlySpeed()));
}
@Override
public void restoreWalkSpeed(Player player, LimboPlayer limbo) {
player.setWalkSpeed(Math.max(player.getWalkSpeed(), limbo.getWalkSpeed()));
}
},
/** Always sets the default speed to the player. */
DEFAULT {
@Override
public void restoreFlySpeed(Player player, LimboPlayer limbo) {
player.setFlySpeed(LimboPlayer.DEFAULT_FLY_SPEED);
}
@Override
public void restoreWalkSpeed(Player player, LimboPlayer limbo) {
player.setWalkSpeed(LimboPlayer.DEFAULT_WALK_SPEED);
}
};
/**
* Restores the fly speed from Limbo to Player according to the restoration type.
*
* @param player the player to modify
* @param limbo the limbo player to read from
*/
public abstract void restoreFlySpeed(Player player, LimboPlayer limbo);
/**
* Restores the walk speed from Limbo to Player according to the restoration type.
*
* @param player the player to modify
* @param limbo the limbo player to read from
*/
public abstract void restoreWalkSpeed(Player player, LimboPlayer limbo);
}

View File

@ -0,0 +1,79 @@
package fr.xephi.authme.data.limbo.persistence;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.LimboSettings;
import org.bukkit.entity.Player;
import javax.inject.Inject;
/**
* Handles the persistence of LimboPlayers.
*/
public class LimboPersistence implements SettingsDependent {
private final Factory<LimboPersistenceHandler> handlerFactory;
private LimboPersistenceHandler handler;
@Inject
LimboPersistence(Settings settings, Factory<LimboPersistenceHandler> handlerFactory) {
this.handlerFactory = handlerFactory;
reload(settings);
}
/**
* Retrieves the LimboPlayer for the given player if available.
*
* @param player the player to retrieve the LimboPlayer for
* @return the player's limbo player, or null if not available
*/
public LimboPlayer getLimboPlayer(Player player) {
try {
return handler.getLimboPlayer(player);
} catch (Exception e) {
ConsoleLogger.logException("Could not get LimboPlayer for '" + player.getName() + "'", e);
}
return null;
}
/**
* Saves the given LimboPlayer for the provided player.
*
* @param player the player to save the LimboPlayer for
* @param limbo the limbo player to save
*/
public void saveLimboPlayer(Player player, LimboPlayer limbo) {
try {
handler.saveLimboPlayer(player, limbo);
} catch (Exception e) {
ConsoleLogger.logException("Could not save LimboPlayer for '" + player.getName() + "'", e);
}
}
/**
* Removes the LimboPlayer for the given player.
*
* @param player the player whose LimboPlayer should be removed
*/
public void removeLimboPlayer(Player player) {
try {
handler.removeLimboPlayer(player);
} catch (Exception e) {
ConsoleLogger.logException("Could not remove LimboPlayer for '" + player.getName() + "'", e);
}
}
@Override
public void reload(Settings settings) {
LimboPersistenceType persistenceType = settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE);
// If we're changing from an existing handler, output a quick hint that nothing is converted.
if (handler != null && handler.getType() != persistenceType) {
ConsoleLogger.info("Limbo persistence type has changed! Note that the data is not converted.");
}
handler = handlerFactory.newInstance(persistenceType.getImplementationClass());
}
}

View File

@ -0,0 +1,39 @@
package fr.xephi.authme.data.limbo.persistence;
import fr.xephi.authme.data.limbo.LimboPlayer;
import org.bukkit.entity.Player;
/**
* Handles I/O for storing LimboPlayer objects.
*/
interface LimboPersistenceHandler {
/**
* Returns the limbo player for the given player if it exists.
*
* @param player the player
* @return the stored limbo player, or null if not available
*/
LimboPlayer getLimboPlayer(Player player);
/**
* Saves the given limbo player for the given player to the disk.
*
* @param player the player to save the limbo player for
* @param limbo the limbo player to save
*/
void saveLimboPlayer(Player player, LimboPlayer limbo);
/**
* Removes the limbo player from the disk.
*
* @param player the player whose limbo player should be removed
*/
void removeLimboPlayer(Player player);
/**
* @return the type of the limbo persistence implementation
*/
LimboPersistenceType getType();
}

View File

@ -0,0 +1,37 @@
package fr.xephi.authme.data.limbo.persistence;
/**
* Types of persistence for LimboPlayer objects.
*/
public enum LimboPersistenceType {
/** Store each LimboPlayer in a separate file. */
INDIVIDUAL_FILES(SeparateFilePersistenceHandler.class),
/** Store all LimboPlayers in the same file. */
SINGLE_FILE(SingleFilePersistenceHandler.class),
/** Distribute LimboPlayers by segments into a set number of files. */
SEGMENT_FILES(SegmentFilesPersistenceHolder.class),
/** No persistence to disk. */
DISABLED(NoOpPersistenceHandler.class);
private final Class<? extends LimboPersistenceHandler> implementationClass;
/**
* Constructor.
*
* @param implementationClass the implementation class
*/
LimboPersistenceType(Class<? extends LimboPersistenceHandler> implementationClass) {
this.implementationClass= implementationClass;
}
/**
* @return class implementing the persistence type
*/
public Class<? extends LimboPersistenceHandler> getImplementationClass() {
return implementationClass;
}
}

View File

@ -0,0 +1,115 @@
package fr.xephi.authme.data.limbo.persistence;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.service.BukkitService;
import org.bukkit.Location;
import org.bukkit.World;
import java.lang.reflect.Type;
import java.util.function.Function;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.CAN_FLY;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.FLY_SPEED;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.GROUP;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.IS_OP;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOCATION;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_PITCH;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_WORLD;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_X;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_Y;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_YAW;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.LOC_Z;
import static fr.xephi.authme.data.limbo.persistence.LimboPlayerSerializer.WALK_SPEED;
/**
* Converts a JsonElement to a LimboPlayer.
*/
class LimboPlayerDeserializer implements JsonDeserializer<LimboPlayer> {
private BukkitService bukkitService;
LimboPlayerDeserializer(BukkitService bukkitService) {
this.bukkitService = bukkitService;
}
@Override
public LimboPlayer deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject == null) {
return null;
}
Location loc = deserializeLocation(jsonObject);
boolean operator = getBoolean(jsonObject, IS_OP);
String group = getString(jsonObject, GROUP);
boolean canFly = getBoolean(jsonObject, CAN_FLY);
float walkSpeed = getFloat(jsonObject, WALK_SPEED, LimboPlayer.DEFAULT_WALK_SPEED);
float flySpeed = getFloat(jsonObject, FLY_SPEED, LimboPlayer.DEFAULT_FLY_SPEED);
return new LimboPlayer(loc, operator, group, canFly, walkSpeed, flySpeed);
}
private Location deserializeLocation(JsonObject jsonObject) {
JsonElement e;
if ((e = jsonObject.getAsJsonObject(LOCATION)) != null) {
JsonObject locationObject = e.getAsJsonObject();
World world = bukkitService.getWorld(getString(locationObject, LOC_WORLD));
if (world != null) {
double x = getDouble(locationObject, LOC_X);
double y = getDouble(locationObject, LOC_Y);
double z = getDouble(locationObject, LOC_Z);
float yaw = getFloat(locationObject, LOC_YAW);
float pitch = getFloat(locationObject, LOC_PITCH);
return new Location(world, x, y, z, yaw, pitch);
}
}
return null;
}
private static String getString(JsonObject jsonObject, String memberName) {
JsonElement element = jsonObject.get(memberName);
return element != null ? element.getAsString() : "";
}
private static boolean getBoolean(JsonObject jsonObject, String memberName) {
JsonElement element = jsonObject.get(memberName);
return element != null && element.getAsBoolean();
}
private static float getFloat(JsonObject jsonObject, String memberName) {
return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsFloat, 0.0f);
}
private static float getFloat(JsonObject jsonObject, String memberName, float defaultValue) {
return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsFloat, defaultValue);
}
private static double getDouble(JsonObject jsonObject, String memberName) {
return getNumberFromElement(jsonObject.get(memberName), JsonElement::getAsDouble, 0.0);
}
/**
* Gets a number from the given JsonElement safely.
*
* @param jsonElement the element to retrieve the number from
* @param numberFunction the function to get the number from the element
* @param defaultValue the value to return if the element is null or the number cannot be retrieved
* @param <N> the number type
* @return the number from the given JSON element, or the default value
*/
private static <N extends Number> N getNumberFromElement(JsonElement jsonElement,
Function<JsonElement, N> numberFunction,
N defaultValue) {
if (jsonElement != null) {
try {
return numberFunction.apply(jsonElement);
} catch (NumberFormatException ignore) {
}
}
return defaultValue;
}
}

View File

@ -0,0 +1,52 @@
package fr.xephi.authme.data.limbo.persistence;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import fr.xephi.authme.data.limbo.LimboPlayer;
import org.bukkit.Location;
import java.lang.reflect.Type;
/**
* Converts a LimboPlayer to a JsonElement.
*/
class LimboPlayerSerializer implements JsonSerializer<LimboPlayer> {
static final String LOCATION = "location";
static final String LOC_WORLD = "world";
static final String LOC_X = "x";
static final String LOC_Y = "y";
static final String LOC_Z = "z";
static final String LOC_YAW = "yaw";
static final String LOC_PITCH = "pitch";
static final String GROUP = "group";
static final String IS_OP = "operator";
static final String CAN_FLY = "can-fly";
static final String WALK_SPEED = "walk-speed";
static final String FLY_SPEED = "fly-speed";
@Override
public JsonElement serialize(LimboPlayer limboPlayer, Type type, JsonSerializationContext context) {
Location loc = limboPlayer.getLocation();
JsonObject locationObject = new JsonObject();
locationObject.addProperty(LOC_WORLD, loc.getWorld().getName());
locationObject.addProperty(LOC_X, loc.getX());
locationObject.addProperty(LOC_Y, loc.getY());
locationObject.addProperty(LOC_Z, loc.getZ());
locationObject.addProperty(LOC_YAW, loc.getYaw());
locationObject.addProperty(LOC_PITCH, loc.getPitch());
JsonObject obj = new JsonObject();
obj.add(LOCATION, locationObject);
obj.addProperty(GROUP, limboPlayer.getGroup());
obj.addProperty(IS_OP, limboPlayer.isOperator());
obj.addProperty(CAN_FLY, limboPlayer.isCanFly());
obj.addProperty(WALK_SPEED, limboPlayer.getWalkSpeed());
obj.addProperty(FLY_SPEED, limboPlayer.getFlySpeed());
return obj;
}
}

View File

@ -0,0 +1,30 @@
package fr.xephi.authme.data.limbo.persistence;
import fr.xephi.authme.data.limbo.LimboPlayer;
import org.bukkit.entity.Player;
/**
* Limbo player persistence implementation that does nothing.
*/
class NoOpPersistenceHandler implements LimboPersistenceHandler {
@Override
public LimboPlayer getLimboPlayer(Player player) {
return null;
}
@Override
public void saveLimboPlayer(Player player, LimboPlayer limbo) {
// noop
}
@Override
public void removeLimboPlayer(Player player) {
// noop
}
@Override
public LimboPersistenceType getType() {
return LimboPersistenceType.DISABLED;
}
}

View File

@ -0,0 +1,94 @@
package fr.xephi.authme.data.limbo.persistence;
/**
* Configuration for the total number of segments to use.
* <p>
* The {@link SegmentFilesPersistenceHolder} reduces the number of files by assigning each UUID
* to a segment. This enum allows to define how many segments the UUIDs should be distributed in.
* <p>
* Segments are defined by a <b>distribution</b> and a <b>length.</b> The distribution defines
* to how many outputs a single hexadecimal characters should be mapped. So e.g. a distribution
* of 3 means that all hexadecimal characters 0-f should be distributed over three different
* outputs evenly. The {@link SegmentNameBuilder} simply uses hexadecimal characters as outputs,
* so e.g. with a distribution of 3 all hex characters 0-f are mapped to 0, 1, or 2.
* <p>
* To ensure an even distribution the segments must be powers of 2. Trivially, to implement a
* distribution of 16, the same character may be returned as was input (since 0-f make up 16
* characters). A distribution of 1, on the other hand, means that the same output is returned
* regardless of the input character.
* <p>
* The <b>length</b> parameter defines how many characters of a player's UUID should be used to
* create the segment ID. In other words, with a distribution of 2 and a length of 3, the first
* three characters of the UUID are taken into consideration, each mapped to one of two possible
* characters. For instance, a UUID starting with "0f5c9321" may yield the segment ID "010."
* Such a segment ID defines in which file the given UUID can be found and stored.
* <p>
* The number of segments such a configuration yields is computed as {@code distribution ^ length},
* since distribution defines how many outputs there are per digit, and length defines the number
* of digits. For instance, a distribution of 2 and a length of 3 will yield segment IDs 000, 001,
* 010, 011, 100, 101, 110 and 111 (i.e. all binary numbers from 0 to 7).
* <p>
* There are multiple possibilities to achieve certain segment totals, e.g. 8 different segments
* may be created by setting distribution to 8 and length to 1, or distr. to 2 and length to 3.
* Where possible, prefer a length of 1 (no string concatenation required) or a distribution of
* 16 (no remapping of the characters required).
*/
public enum SegmentConfiguration {
/** 1. */
ONE(1, 1),
///** 2. */
//TWO(2, 1),
/** 4. */
FOUR(4, 1),
/** 8. */
EIGHT(8, 1),
/** 16. */
SIXTEEN(16, 1),
/** 32. */
THIRTY_TWO(2, 5),
/** 64. */
SIXTY_FOUR(4, 3),
/** 128. */
ONE_TWENTY(2, 7),
/** 256. */
TWO_FIFTY(16, 2);
private final int distribution;
private final int length;
SegmentConfiguration(int distribution, int length) {
this.distribution = distribution;
this.length = length;
}
/**
* @return the distribution size per character, i.e. how many possible outputs there are
* for any hexadecimal character
*/
public int getDistribution() {
return distribution;
}
/**
* @return number of characters from a UUID that should be used to create a segment ID
*/
public int getLength() {
return length;
}
/**
* @return number of segments to which this configuration will distribute UUIDs
*/
public int getTotalSegments() {
return (int) Math.pow(distribution, length);
}
}

View File

@ -0,0 +1,226 @@
package fr.xephi.authme.data.limbo.persistence;
import com.google.common.io.Files;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.LimboSettings;
import fr.xephi.authme.util.FileUtils;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* Persistence handler for LimboPlayer objects by distributing the objects to store
* in various segments (buckets) based on the start of the player's UUID.
*/
class SegmentFilesPersistenceHolder implements LimboPersistenceHandler {
private static final Type LIMBO_MAP_TYPE = new TypeToken<Map<String, LimboPlayer>>(){}.getType();
private final File cacheFolder;
private final Gson gson;
private final SegmentNameBuilder segmentNameBuilder;
@Inject
SegmentFilesPersistenceHolder(@DataFolder File dataFolder, BukkitService bukkitService, Settings settings) {
cacheFolder = new File(dataFolder, "playerdata");
if (!cacheFolder.exists()) {
// TODO ljacqu 20170313: Create FileUtils#mkdirs
cacheFolder.mkdirs();
}
gson = new GsonBuilder()
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService))
.setPrettyPrinting()
.create();
segmentNameBuilder = new SegmentNameBuilder(settings.getProperty(LimboSettings.SEGMENT_DISTRIBUTION));
convertOldDataToCurrentSegmentScheme();
deleteEmptyFiles();
}
@Override
public LimboPlayer getLimboPlayer(Player player) {
String uuid = PlayerUtils.getUUIDorName(player);
File file = getPlayerSegmentFile(uuid);
Map<String, LimboPlayer> entries = readLimboPlayers(file);
return entries == null ? null : entries.get(uuid);
}
@Override
public void saveLimboPlayer(Player player, LimboPlayer limbo) {
String uuid = PlayerUtils.getUUIDorName(player);
File file = getPlayerSegmentFile(uuid);
Map<String, LimboPlayer> entries = null;
if (file.exists()) {
entries = readLimboPlayers(file);
} else {
FileUtils.create(file);
}
/* intentionally separate if */
if (entries == null) {
entries = new HashMap<>();
}
entries.put(PlayerUtils.getUUIDorName(player), limbo);
saveEntries(entries, file);
}
@Override
public void removeLimboPlayer(Player player) {
String uuid = PlayerUtils.getUUIDorName(player);
File file = getPlayerSegmentFile(uuid);
if (file.exists()) {
Map<String, LimboPlayer> entries = readLimboPlayers(file);
if (entries != null && entries.remove(PlayerUtils.getUUIDorName(player)) != null) {
saveEntries(entries, file);
}
}
}
@Override
public LimboPersistenceType getType() {
return LimboPersistenceType.SEGMENT_FILES;
}
private void saveEntries(Map<String, LimboPlayer> entries, File file) {
try (FileWriter fw = new FileWriter(file)) {
gson.toJson(entries, fw);
} catch (Exception e) {
ConsoleLogger.logException("Could not write to '" + file + "':", e);
}
}
private Map<String, LimboPlayer> readLimboPlayers(File file) {
if (!file.exists()) {
return null;
}
try {
return gson.fromJson(Files.toString(file, StandardCharsets.UTF_8), LIMBO_MAP_TYPE);
} catch (Exception e) {
ConsoleLogger.logException("Failed reading '" + file + "':", e);
}
return null;
}
private File getPlayerSegmentFile(String uuid) {
String segment = segmentNameBuilder.createSegmentName(uuid);
return new File(cacheFolder, segment + "-limbo.json");
}
/**
* Loads segment files in the cache folder that don't correspond to the current segmenting scheme
* and migrates the data into files of the current segments. This allows a player to change the
* segment size without any loss of data.
*/
private void convertOldDataToCurrentSegmentScheme() {
String currentPrefix = segmentNameBuilder.getPrefix();
File[] files = listFiles(cacheFolder);
Map<String, LimboPlayer> allLimboPlayers = new HashMap<>();
List<File> migratedFiles = new ArrayList<>();
for (File file : files) {
if (isLimboJsonFile(file) && !file.getName().startsWith(currentPrefix)) {
Map<String, LimboPlayer> data = readLimboPlayers(file);
if (data != null) {
allLimboPlayers.putAll(data);
migratedFiles.add(file);
}
}
}
if (!allLimboPlayers.isEmpty()) {
saveToNewSegments(allLimboPlayers);
migratedFiles.forEach(FileUtils::delete);
}
}
/**
* Saves the LimboPlayer data read from old segmenting schemes into the current segmenting scheme.
*
* @param limbosFromOldSegments the limbo players to store into the current segment files
*/
private void saveToNewSegments(Map<String, LimboPlayer> limbosFromOldSegments) {
Map<String, Map<String, LimboPlayer>> limboBySegment = groupBySegment(limbosFromOldSegments);
ConsoleLogger.info("Saving " + limbosFromOldSegments.size() + " LimboPlayers from old segments into "
+ limboBySegment.size() + " current segments");
for (Map.Entry<String, Map<String, LimboPlayer>> entry : limboBySegment.entrySet()) {
File file = new File(cacheFolder, entry.getKey() + "-limbo.json");
Map<String, LimboPlayer> limbosToSave = Optional.ofNullable(readLimboPlayers(file))
.orElseGet(HashMap::new);
limbosToSave.putAll(entry.getValue());
saveEntries(limbosToSave, file);
}
}
/**
* Converts a Map of UUID to LimboPlayers to a 2-dimensional Map of LimboPlayers by segment ID and UUID.
* {@code Map(uuid -> LimboPlayer) to Map(segment -> Map(uuid -> LimboPlayer))}
*
* @param readLimboPlayers the limbo players to order by segment
* @return limbo players ordered by segment ID and associated player UUID
*/
private Map<String, Map<String, LimboPlayer>> groupBySegment(Map<String, LimboPlayer> readLimboPlayers) {
Map<String, Map<String, LimboPlayer>> limboBySegment = new HashMap<>();
for (Map.Entry<String, LimboPlayer> entry : readLimboPlayers.entrySet()) {
String segmentId = segmentNameBuilder.createSegmentName(entry.getKey());
limboBySegment.computeIfAbsent(segmentId, s -> new HashMap<>())
.put(entry.getKey(), entry.getValue());
}
return limboBySegment;
}
/**
* Deletes files from the current segmenting scheme that are empty.
*/
private void deleteEmptyFiles() {
File[] files = listFiles(cacheFolder);
long deletedFiles = Arrays.stream(files)
// typically the size is 2 because there's an empty JSON map: {}
.filter(f -> isLimboJsonFile(f) && f.length() < 3)
.peek(FileUtils::delete)
.count();
ConsoleLogger.debug("Limbo: Deleted {0} empty segment files", deletedFiles);
}
/**
* @param file the file to check
* @return true if it is a segment file storing Limbo data, false otherwise
*/
private static boolean isLimboJsonFile(File file) {
String name = file.getName();
return name.startsWith("seg") && name.endsWith("-limbo.json");
}
private static File[] listFiles(File folder) {
File[] files = folder.listFiles();
if (files == null) {
ConsoleLogger.warning("Could not get files of '" + folder + "'");
return new File[0];
}
return files;
}
}

View File

@ -0,0 +1,73 @@
package fr.xephi.authme.data.limbo.persistence;
import java.util.HashMap;
import java.util.Map;
/**
* Creates segment names for {@link SegmentFilesPersistenceHolder}.
*/
class SegmentNameBuilder {
private final int length;
private final int distribution;
private final String prefix;
private final Map<Character, Character> charToSegmentChar;
/**
* Constructor.
*
* @param partition the segment configuration
*/
SegmentNameBuilder(SegmentConfiguration partition) {
this.length = partition.getLength();
this.distribution = partition.getDistribution();
this.prefix = "seg" + partition.getTotalSegments() + "-";
this.charToSegmentChar = buildCharMap(distribution);
}
/**
* Returns the segment ID for the given UUID.
*
* @param uuid the player's uuid to get the segment for
* @return id the uuid belongs to
*/
String createSegmentName(String uuid) {
if (distribution == 16) {
return prefix + uuid.substring(0, length);
} else {
return prefix + buildSegmentName(uuid.substring(0, length).toCharArray());
}
}
/**
* @return the prefix used for the current segment configuration
*/
String getPrefix() {
return prefix;
}
private String buildSegmentName(char[] chars) {
if (chars.length == 1) {
return String.valueOf(charToSegmentChar.get(chars[0]));
}
StringBuilder sb = new StringBuilder(chars.length);
for (char chr : chars) {
sb.append(charToSegmentChar.get(chr));
}
return sb.toString();
}
private static Map<Character, Character> buildCharMap(int distribution) {
final char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
final int divisor = 16 / distribution;
Map<Character, Character> charToSegmentChar = new HashMap<>();
for (int i = 0; i < hexChars.length; ++i) {
int mappedChar = i / divisor;
charToSegmentChar.put(hexChars[i], hexChars[mappedChar]);
}
return charToSegmentChar;
}
}

View File

@ -0,0 +1,90 @@
package fr.xephi.authme.data.limbo.persistence;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.util.FileUtils;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* Saves LimboPlayer objects as JSON into individual files.
*/
class SeparateFilePersistenceHandler implements LimboPersistenceHandler {
private final Gson gson;
private final File cacheDir;
@Inject
SeparateFilePersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService) {
cacheDir = new File(dataFolder, "playerdata");
if (!cacheDir.exists() && !cacheDir.isDirectory() && !cacheDir.mkdir()) {
ConsoleLogger.warning("Failed to create playerdata directory '" + cacheDir + "'");
}
gson = new GsonBuilder()
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService))
.setPrettyPrinting()
.create();
}
@Override
public LimboPlayer getLimboPlayer(Player player) {
String id = PlayerUtils.getUUIDorName(player);
File file = new File(cacheDir, id + File.separator + "data.json");
if (!file.exists()) {
return null;
}
try {
String str = Files.toString(file, StandardCharsets.UTF_8);
return gson.fromJson(str, LimboPlayer.class);
} catch (IOException e) {
ConsoleLogger.logException("Could not read player data on disk for '" + player.getName() + "'", e);
return null;
}
}
@Override
public void saveLimboPlayer(Player player, LimboPlayer limboPlayer) {
String id = PlayerUtils.getUUIDorName(player);
try {
File file = new File(cacheDir, id + File.separator + "data.json");
Files.createParentDirs(file);
Files.touch(file);
Files.write(gson.toJson(limboPlayer), file, StandardCharsets.UTF_8);
} catch (IOException e) {
ConsoleLogger.logException("Failed to write " + player.getName() + " data:", e);
}
}
/**
* Removes the LimboPlayer. This will delete the
* "playerdata/&lt;uuid or name&gt;/" folder from disk.
*
* @param player player to remove
*/
@Override
public void removeLimboPlayer(Player player) {
String id = PlayerUtils.getUUIDorName(player);
File file = new File(cacheDir, id);
if (file.exists()) {
FileUtils.purgeDirectory(file);
FileUtils.delete(file);
}
}
@Override
public LimboPersistenceType getType() {
return LimboPersistenceType.INDIVIDUAL_FILES;
}
}

View File

@ -0,0 +1,94 @@
package fr.xephi.authme.data.limbo.persistence;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.util.FileUtils;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Saves all LimboPlayers in one JSON file and keeps the entries in memory.
*/
class SingleFilePersistenceHandler implements LimboPersistenceHandler {
private final File cacheFile;
private final Gson gson;
private Map<String, LimboPlayer> entries;
@Inject
SingleFilePersistenceHandler(@DataFolder File dataFolder, BukkitService bukkitService) {
cacheFile = new File(dataFolder, "limbo.json");
if (!cacheFile.exists()) {
FileUtils.create(cacheFile);
}
gson = new GsonBuilder()
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerSerializer())
.registerTypeAdapter(LimboPlayer.class, new LimboPlayerDeserializer(bukkitService))
.setPrettyPrinting()
.create();
Type type = new TypeToken<ConcurrentMap<String, LimboPlayer>>(){}.getType();
try (FileReader fr = new FileReader(cacheFile)) {
entries = gson.fromJson(fr, type);
} catch (IOException e) {
ConsoleLogger.logException("Failed to read from '" + cacheFile + "':", e);
}
if (entries == null) {
entries = new ConcurrentHashMap<>();
}
}
@Override
public LimboPlayer getLimboPlayer(Player player) {
return entries.get(PlayerUtils.getUUIDorName(player));
}
@Override
public void saveLimboPlayer(Player player, LimboPlayer limbo) {
entries.put(PlayerUtils.getUUIDorName(player), limbo);
saveEntries("adding '" + player.getName() + "'");
}
@Override
public void removeLimboPlayer(Player player) {
LimboPlayer entry = entries.remove(PlayerUtils.getUUIDorName(player));
if (entry != null) {
saveEntries("removing '" + player.getName() + "'");
}
}
/**
* Saves the entries to the disk.
*
* @param action the reason for saving (for logging purposes)
*/
private void saveEntries(String action) {
try (FileWriter fw = new FileWriter(cacheFile)) {
gson.toJson(entries, fw);
} catch (IOException e) {
ConsoleLogger.logException("Failed saving JSON limbo cache after " + action, e);
}
}
@Override
public LimboPersistenceType getType() {
return LimboPersistenceType.SINGLE_FILE;
}
}

View File

@ -6,6 +6,8 @@ import fr.xephi.authme.settings.properties.DatabaseSettings;
/**
* Database column names.
*/
// Justification: String is immutable and this class is used to easily access the configurable column names
@SuppressWarnings({"checkstyle:VisibilityModifier", "checkstyle:MemberName", "checkstyle:AbbreviationAsWordInName"})
public final class Columns {
public final String NAME;

View File

@ -7,7 +7,7 @@ import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.security.crypts.XFBCRYPT;
import fr.xephi.authme.security.crypts.XfBCrypt;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.settings.properties.HooksSettings;
@ -31,7 +31,7 @@ import java.util.Set;
public class MySQL implements DataSource {
private boolean useSSL;
private boolean useSsl;
private String host;
private String port;
private String username;
@ -45,7 +45,10 @@ public class MySQL implements DataSource {
private HikariDataSource ds;
private String phpBbPrefix;
private String ipbPrefix;
private int phpBbGroup;
private int ipbGroup;
private int xfGroup;
private String wordpressPrefix;
public MySQL(Settings settings) throws ClassNotFoundException, SQLException {
@ -96,12 +99,15 @@ public class MySQL implements DataSource {
this.hashAlgorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH);
this.phpBbPrefix = settings.getProperty(HooksSettings.PHPBB_TABLE_PREFIX);
this.phpBbGroup = settings.getProperty(HooksSettings.PHPBB_ACTIVATED_GROUP_ID);
this.ipbPrefix = settings.getProperty(HooksSettings.IPB_TABLE_PREFIX);
this.ipbGroup = settings.getProperty(HooksSettings.IPB_ACTIVATED_GROUP_ID);
this.xfGroup = settings.getProperty(HooksSettings.XF_ACTIVATED_GROUP_ID);
this.wordpressPrefix = settings.getProperty(HooksSettings.WORDPRESS_TABLE_PREFIX);
this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE);
if (poolSize == -1) {
poolSize = Utils.getCoreCount();
poolSize = Utils.getCoreCount()*3;
}
this.useSSL = settings.getProperty(DatabaseSettings.MYSQL_USE_SSL);
this.useSsl = settings.getProperty(DatabaseSettings.MYSQL_USE_SSL);
}
private void setConnectionArguments() {
@ -119,7 +125,7 @@ public class MySQL implements DataSource {
ds.setPassword(this.password);
// Request mysql over SSL
ds.addDataSourceProperty("useSSL", useSSL);
ds.addDataSourceProperty("useSSL", useSsl);
// Encoding
ds.addDataSourceProperty("characterEncoding", "utf8");
@ -286,7 +292,7 @@ public class MySQL implements DataSource {
if (rs.next()) {
Blob blob = rs.getBlob("data");
byte[] bytes = blob.getBytes(1, (int) blob.length());
auth.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes)));
auth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes)));
}
}
}
@ -334,8 +340,39 @@ public class MySQL implements DataSource {
pst.close();
}
}
if (hashAlgorithm == HashAlgorithm.PHPBB) {
if (hashAlgorithm == HashAlgorithm.IPB4){
sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
pst = con.prepareStatement(sql);
pst.setString(1, auth.getNickname());
rs = pst.executeQuery();
if (rs.next()){
// Update player group in core_members
sql = "UPDATE " + ipbPrefix + tableName + " SET "+ tableName + ".member_group_id=? WHERE " + col.NAME + "=?;";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, ipbGroup);
pst2.setString(2, auth.getNickname());
pst2.executeUpdate();
pst2.close();
// Get current time without ms
long time = System.currentTimeMillis() / 1000;
// update joined date
sql = "UPDATE " + ipbPrefix + tableName + " SET "+ tableName + ".joined=? WHERE " + col.NAME + "=?;";
pst2 = con.prepareStatement(sql);
pst2.setLong(1, time);
pst2.setString(2, auth.getNickname());
pst2.executeUpdate();
pst2.close();
// Update last_visit
sql = "UPDATE " + ipbPrefix + tableName + " SET " + tableName + ".last_visit=? WHERE " + col.NAME + "=?;";
pst2 = con.prepareStatement(sql);
pst2.setLong(1, time);
pst2.setString(2, auth.getNickname());
pst2.executeUpdate();
pst2.close();
}
rs.close();
pst.close();
} else if (hashAlgorithm == HashAlgorithm.PHPBB) {
sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
pst = con.prepareStatement(sql);
pst.setString(1, auth.getNickname());
@ -477,19 +514,53 @@ public class MySQL implements DataSource {
pst = con.prepareStatement("SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;");
pst.setString(1, auth.getNickname());
rs = pst.executeQuery();
if (rs.next()) {
if (rs.next()) {
int id = rs.getInt(col.ID);
// Insert player password, salt in xf_user_authenticate
sql = "INSERT INTO xf_user_authenticate (user_id, scheme_class, data) VALUES (?,?,?)";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, id);
pst2.setString(2, XFBCRYPT.SCHEME_CLASS);
String serializedHash = XFBCRYPT.serializeHash(auth.getPassword().getHash());
pst2.setString(2, XfBCrypt.SCHEME_CLASS);
String serializedHash = XfBCrypt.serializeHash(auth.getPassword().getHash());
byte[] bytes = serializedHash.getBytes();
Blob blob = con.createBlob();
blob.setBytes(1, bytes);
pst2.setBlob(3, blob);
pst2.executeUpdate();
pst2.close();
// Update player group in xf_users
sql = "UPDATE " + tableName + " SET "+ tableName + ".user_group_id=? WHERE " + col.NAME + "=?;";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, xfGroup);
pst2.setString(2, auth.getNickname());
pst2.executeUpdate();
pst2.close();
// Update player permission combination in xf_users
sql = "UPDATE " + tableName + " SET "+ tableName + ".permission_combination_id=? WHERE " + col.NAME + "=?;";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, xfGroup);
pst2.setString(2, auth.getNickname());
pst2.executeUpdate();
pst2.close();
// Insert player privacy combination in xf_user_privacy
sql = "INSERT INTO xf_user_privacy (user_id, allow_view_profile, allow_post_profile, allow_send_personal_conversation, allow_view_identities, allow_receive_news_feed) VALUES (?,?,?,?,?,?)";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, id);
pst2.setString(2, "everyone");
pst2.setString(3, "members");
pst2.setString(4, "members");
pst2.setString(5, "everyone");
pst2.setString(6, "everyone");
pst2.executeUpdate();
pst2.close();
// Insert player group relation in xf_user_group_relation
sql = "INSERT INTO xf_user_group_relation (user_id, user_group_id, is_primary) VALUES (?,?,?)";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, id);
pst2.setInt(2, xfGroup);
pst2.setString(3, "1");
pst2.executeUpdate();
pst2.close();
}
rs.close();
pst.close();
@ -538,7 +609,7 @@ public class MySQL implements DataSource {
// Insert password in the correct table
sql = "UPDATE xf_user_authenticate SET data=? WHERE " + col.ID + "=?;";
PreparedStatement pst2 = con.prepareStatement(sql);
String serializedHash = XFBCRYPT.serializeHash(password.getHash());
String serializedHash = XfBCrypt.serializeHash(password.getHash());
byte[] bytes = serializedHash.getBytes();
Blob blob = con.createBlob();
blob.setBytes(1, bytes);
@ -549,7 +620,7 @@ public class MySQL implements DataSource {
// ...
sql = "UPDATE xf_user_authenticate SET scheme_class=? WHERE " + col.ID + "=?;";
pst2 = con.prepareStatement(sql);
pst2.setString(1, XFBCRYPT.SCHEME_CLASS);
pst2.setString(1, XfBCrypt.SCHEME_CLASS);
pst2.setInt(2, id);
pst2.executeUpdate();
pst2.close();
@ -824,7 +895,7 @@ public class MySQL implements DataSource {
if (rs2.next()) {
Blob blob = rs2.getBlob("data");
byte[] bytes = blob.getBytes(1, (int) blob.length());
pAuth.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes)));
pAuth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes)));
}
rs2.close();
}
@ -856,7 +927,7 @@ public class MySQL implements DataSource {
if (rs2.next()) {
Blob blob = rs2.getBlob("data");
byte[] bytes = blob.getBytes(1, (int) blob.length());
pAuth.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes)));
pAuth.setPassword(new HashedPassword(XfBCrypt.getHashFromBlob(bytes)));
}
rs2.close();
}

View File

@ -37,7 +37,7 @@ public abstract class AbstractDataSourceConverter<S extends DataSource> implemen
// which is never the case when a converter is launched from the /authme converter command.
@Override
public void execute(CommandSender sender) {
if (!destinationType.equals(destination.getType())) {
if (destinationType != destination.getType()) {
if (sender != null) {
sender.sendMessage("Please configure your connection to "
+ destinationType + " and re-run this command");
@ -59,6 +59,7 @@ public abstract class AbstractDataSourceConverter<S extends DataSource> implemen
if (destination.isAuthAvailable(auth.getNickname())) {
skippedPlayers.add(auth.getNickname());
} else {
adaptPlayerAuth(auth);
destination.saveAuth(auth);
destination.updateQuitLoc(auth);
}
@ -72,6 +73,15 @@ public abstract class AbstractDataSourceConverter<S extends DataSource> implemen
+ " to " + destinationType);
}
/**
* Adapts the PlayerAuth from the source before it is saved in the destination.
*
* @param auth the auth from the source
*/
protected void adaptPlayerAuth(PlayerAuth auth) {
// noop
}
/**
* @return the data source to convert from
* @throws Exception during initialization of source

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.datasource.converter;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.FlatFile;
@ -25,4 +26,11 @@ public class ForceFlatToSqlite extends AbstractDataSourceConverter<FlatFile> {
public FlatFile getSource() {
return source;
}
@Override
protected void adaptPlayerAuth(PlayerAuth auth) {
// Issue #1120: FlatFile returns PlayerAuth objects with realname = lower-case name all the time.
// We don't want to take this over into the new data source.
auth.setRealName("Player");
}
}

View File

@ -16,6 +16,7 @@ import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
@ -40,43 +41,41 @@ public class RakamakConverter implements Converter {
@Override
// TODO ljacqu 20151229: Restructure this into smaller portions
public void execute(CommandSender sender) {
boolean useIP = settings.getProperty(ConverterSettings.RAKAMAK_USE_IP);
boolean useIp = settings.getProperty(ConverterSettings.RAKAMAK_USE_IP);
String fileName = settings.getProperty(ConverterSettings.RAKAMAK_FILE_NAME);
String ipFileName = settings.getProperty(ConverterSettings.RAKAMAK_IP_FILE_NAME);
File source = new File(pluginFolder, fileName);
File ipfiles = new File(pluginFolder, ipFileName);
HashMap<String, String> playerIP = new HashMap<>();
HashMap<String, HashedPassword> playerPSW = new HashMap<>();
File ipFiles = new File(pluginFolder, ipFileName);
Map<String, String> playerIp = new HashMap<>();
Map<String, HashedPassword> playerPassword = new HashMap<>();
try {
BufferedReader users;
BufferedReader ipFile;
ipFile = new BufferedReader(new FileReader(ipfiles));
BufferedReader ipFile = new BufferedReader(new FileReader(ipFiles));
String line;
if (useIP) {
if (useIp) {
String tempLine;
while ((tempLine = ipFile.readLine()) != null) {
if (tempLine.contains("=")) {
String[] args = tempLine.split("=");
playerIP.put(args[0], args[1]);
playerIp.put(args[0], args[1]);
}
}
}
ipFile.close();
users = new BufferedReader(new FileReader(source));
BufferedReader users = new BufferedReader(new FileReader(source));
while ((line = users.readLine()) != null) {
if (line.contains("=")) {
String[] arguments = line.split("=");
HashedPassword hashedPassword = passwordSecurity.computeHash(arguments[1], arguments[0]);
playerPSW.put(arguments[0], hashedPassword);
playerPassword.put(arguments[0], hashedPassword);
}
}
users.close();
for (Entry<String, HashedPassword> m : playerPSW.entrySet()) {
for (Entry<String, HashedPassword> m : playerPassword.entrySet()) {
String playerName = m.getKey();
HashedPassword psw = playerPSW.get(playerName);
String ip = useIP ? playerIP.get(playerName) : "127.0.0.1";
HashedPassword psw = playerPassword.get(playerName);
String ip = useIp ? playerIp.get(playerName) : "127.0.0.1";
PlayerAuth auth = PlayerAuth.builder()
.name(playerName)
.realName(playerName)

View File

@ -16,13 +16,13 @@ import java.util.UUID;
import static fr.xephi.authme.util.FileUtils.makePath;
public class vAuthConverter implements Converter {
public class VAuthConverter implements Converter {
private final DataSource dataSource;
private final File vAuthPasswordsFile;
@Inject
vAuthConverter(@DataFolder File dataFolder, DataSource dataSource) {
VAuthConverter(@DataFolder File dataFolder, DataSource dataSource) {
vAuthPasswordsFile = new File(dataFolder.getParent(), makePath("vAuth", "passwords.yml"));
this.dataSource = dataSource;
}

View File

@ -6,7 +6,7 @@ import de.luricos.bukkit.xAuth.xAuth;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.Utils;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.PluginManager;
@ -21,7 +21,7 @@ import java.util.List;
import static fr.xephi.authme.util.FileUtils.makePath;
public class xAuthConverter implements Converter {
public class XAuthConverter implements Converter {
@Inject
@DataFolder
@ -31,7 +31,7 @@ public class xAuthConverter implements Converter {
@Inject
private PluginManager pluginManager;
xAuthConverter() {
XAuthConverter() {
}
@Override
@ -55,7 +55,7 @@ public class xAuthConverter implements Converter {
sender.sendMessage("[AuthMe] xAuth H2 database not found, checking for MySQL or SQLite data...");
}
List<Integer> players = getXAuthPlayers();
if (CollectionUtils.isEmpty(players)) {
if (Utils.isCollectionEmpty(players)) {
sender.sendMessage("[AuthMe] Error while importing xAuthPlayers: did not find any players");
return;
}

View File

@ -2,15 +2,14 @@ package fr.xephi.authme.initialization;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.backup.LimboPlayerStorage;
import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.PluginHookService;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.SpawnLoader;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.ValidationService;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@ -28,17 +27,15 @@ public class OnShutdownPlayerSaver {
@Inject
private ValidationService validationService;
@Inject
private LimboCache limboCache;
@Inject
private DataSource dataSource;
@Inject
private LimboPlayerStorage limboPlayerStorage;
@Inject
private SpawnLoader spawnLoader;
@Inject
private PluginHookService pluginHookService;
@Inject
private PlayerCache playerCache;
@Inject
private LimboService limboService;
OnShutdownPlayerSaver() {
}
@ -57,9 +54,8 @@ public class OnShutdownPlayerSaver {
if (pluginHookService.isNpc(player) || validationService.isUnrestricted(name)) {
return;
}
if (limboCache.hasPlayerData(name)) {
limboCache.restoreData(player);
limboCache.removeFromCache(player);
if (limboService.hasLimboPlayer(name)) {
limboService.restoreData(player);
} else {
saveLoggedinPlayer(player);
}
@ -75,9 +71,5 @@ public class OnShutdownPlayerSaver {
.location(loc).build();
dataSource.updateQuitLoc(auth);
}
if (settings.getProperty(RestrictionSettings.TELEPORT_UNAUTHED_TO_SPAWN)
&& !settings.getProperty(RestrictionSettings.NO_TELEPORT) && !limboPlayerStorage.hasData(player)) {
limboPlayerStorage.saveData(player);
}
}
}

View File

@ -1,11 +1,16 @@
package fr.xephi.authme.initialization;
import ch.jalu.injector.exceptions.InjectorReflectionException;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.security.crypts.description.Recommendation;
import fr.xephi.authme.security.crypts.description.Usage;
import org.bstats.Metrics;
import fr.xephi.authme.output.ConsoleFilter;
import fr.xephi.authme.output.Log4JFilter;
import fr.xephi.authme.service.BukkitService;
@ -18,10 +23,9 @@ import fr.xephi.authme.util.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.mcstats.Metrics;
import javax.inject.Inject;
import java.io.IOException;
import java.util.Optional;
import java.util.logging.Logger;
import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE;
@ -44,41 +48,35 @@ public class OnStartupTasks {
OnStartupTasks() {
}
/**
* Sends bstats metrics.
*
* @param plugin the plugin instance
* @param settings the settings
*/
public static void sendMetrics(AuthMe plugin, Settings settings) {
try {
final Metrics metrics = new Metrics(plugin);
final Metrics metrics = new Metrics(plugin);
final Metrics.Graph languageGraph = metrics.createGraph("Messages Language");
final String messagesLanguage = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
languageGraph.addPlotter(new Metrics.Plotter(messagesLanguage) {
@Override
public int getValue() {
return 1;
}
});
metrics.addCustomChart(new Metrics.SimplePie("messages_language") {
@Override
public String getValue() {
return settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
}
});
final Metrics.Graph databaseBackend = metrics.createGraph("Database Backend");
final String dataSource = settings.getProperty(DatabaseSettings.BACKEND).toString();
databaseBackend.addPlotter(new Metrics.Plotter(dataSource) {
@Override
public int getValue() {
return 1;
}
});
// Submit metrics
metrics.start();
} catch (IOException e) {
// Failed to submit the metrics data
ConsoleLogger.logException("Can't send Metrics data! The plugin will work anyway...", e);
}
metrics.addCustomChart(new Metrics.SimplePie("database_backend") {
@Override
public String getValue() {
return settings.getProperty(DatabaseSettings.BACKEND).toString();
}
});
}
/**
* Sets up the console filter if enabled.
*
* @param settings the settings
* @param logger the plugin logger
* @param logger the plugin logger
*/
public static void setupConsoleFilter(Settings settings, Logger logger) {
if (!settings.getProperty(SecuritySettings.REMOVE_PASSWORD_FROM_CONSOLE)) {
@ -124,4 +122,42 @@ public class OnStartupTasks {
}
}, 1, TICKS_PER_MINUTE * settings.getProperty(EmailSettings.DELAY_RECALL));
}
/**
* Displays a hint to use the legacy AuthMe JAR if AuthMe could not be started
* because Gson was not found.
*
* @param e the exception to process
*/
public static void displayLegacyJarHint(Exception e) {
if (e instanceof InjectorReflectionException) {
Throwable causeOfCause = Optional.of(e)
.map(Throwable::getCause)
.map(Throwable::getCause).orElse(null);
if (causeOfCause instanceof NoClassDefFoundError
&& "Lcom/google/gson/Gson;".equals(causeOfCause.getMessage())) {
ConsoleLogger.warning("YOU MUST DOWNLOAD THE LEGACY JAR TO USE AUTHME ON YOUR SERVER");
ConsoleLogger.warning("Get authme-legacy.jar from http://ci.xephi.fr/job/AuthMeReloaded/");
}
}
}
/**
* Returns whether the hash algorithm is deprecated and won't be able
* to be actively used anymore in 5.4.
*
* @param hash the hash algorithm to check
* @return true if the hash will be deprecated, false otherwise
* @see <a href="https://github.com/Xephi/AuthMeReloaded/issues/1016">#1016</a>
*/
public static boolean isHashDeprecatedIn54(HashAlgorithm hash) {
if (hash.getClazz() == null || hash == HashAlgorithm.PLAINTEXT) {
// Exclude PLAINTEXT from this check because it already has a mandatory migration, which takes care of
// sending all the necessary messages and warnings.
return false;
}
Recommendation recommendation = hash.getClazz().getAnnotation(Recommendation.class);
return recommendation != null && recommendation.value() == Usage.DEPRECATED;
}
}

View File

@ -0,0 +1,19 @@
package fr.xephi.authme.initialization.factory;
/**
* Injectable factory that creates new instances of a certain type.
*
* @param <P> the parent type to which the factory is limited to
*/
public interface Factory<P> {
/**
* Creates an instance of the given class.
*
* @param clazz the class to instantiate
* @param <C> the class type
* @return new instance of the class
*/
<C extends P> C newInstance(Class<C> clazz);
}

View File

@ -0,0 +1,46 @@
package fr.xephi.authme.initialization.factory;
import ch.jalu.injector.Injector;
import ch.jalu.injector.context.ResolvedInstantiationContext;
import ch.jalu.injector.handlers.dependency.DependencyHandler;
import ch.jalu.injector.handlers.instantiation.DependencyDescription;
import ch.jalu.injector.utils.ReflectionUtils;
/**
* Dependency handler that builds {@link Factory} objects.
*/
public class FactoryDependencyHandler implements DependencyHandler {
@Override
public Object resolveValue(ResolvedInstantiationContext<?> context, DependencyDescription dependencyDescription) {
if (dependencyDescription.getType() == Factory.class) {
Class<?> genericType = ReflectionUtils.getGenericType(dependencyDescription.getGenericType());
if (genericType == null) {
throw new IllegalStateException("Factory fields must have concrete generic type. "
+ "Cannot get generic type for field in '" + context.getMappedClass() + "'");
}
return new FactoryImpl<>(genericType, context.getInjector());
}
return null;
}
private static final class FactoryImpl<P> implements Factory<P> {
private final Injector injector;
private final Class<P> parentClass;
FactoryImpl(Class<P> parentClass, Injector injector) {
this.parentClass = parentClass;
this.injector = injector;
}
@Override
public <C extends P> C newInstance(Class<C> clazz) {
if (parentClass.isAssignableFrom(clazz)) {
return injector.newInstance(clazz);
}
throw new IllegalArgumentException(clazz + " not child of " + parentClass);
}
}
}

View File

@ -0,0 +1,37 @@
package fr.xephi.authme.initialization.factory;
import java.util.Collection;
/**
* Injectable object to retrieve and create singletons of a common parent.
*
* @param <P> the parent class to which this store is limited to
*/
public interface SingletonStore<P> {
/**
* Returns the singleton of the given type, creating it if it hasn't been yet created.
*
* @param clazz the class to get the singleton for
* @param <C> type of the singleton
* @return the singleton of type {@code C}
*/
<C extends P> C getSingleton(Class<C> clazz);
/**
* Returns all existing singletons of this store's type.
*
* @return all registered singletons of type {@code P}
*/
Collection<P> retrieveAllOfType();
/**
* Returns all existing singletons of the given type.
*
* @param clazz the type to get singletons for
* @param <C> class type
* @return all registered singletons of type {@code C}
*/
<C extends P> Collection<C> retrieveAllOfType(Class<C> clazz);
}

View File

@ -0,0 +1,61 @@
package fr.xephi.authme.initialization.factory;
import ch.jalu.injector.Injector;
import ch.jalu.injector.context.ResolvedInstantiationContext;
import ch.jalu.injector.handlers.dependency.DependencyHandler;
import ch.jalu.injector.handlers.instantiation.DependencyDescription;
import ch.jalu.injector.utils.ReflectionUtils;
import java.util.Collection;
/**
* Dependency handler that builds {@link SingletonStore} objects.
*/
public class SingletonStoreDependencyHandler implements DependencyHandler {
@Override
public Object resolveValue(ResolvedInstantiationContext<?> context, DependencyDescription dependencyDescription) {
if (dependencyDescription.getType() == SingletonStore.class) {
Class<?> genericType = ReflectionUtils.getGenericType(dependencyDescription.getGenericType());
if (genericType == null) {
throw new IllegalStateException("Singleton store fields must have concrete generic type. "
+ "Cannot get generic type for field in '" + context.getMappedClass() + "'");
}
return new SingletonStoreImpl<>(genericType, context.getInjector());
}
return null;
}
private static final class SingletonStoreImpl<P> implements SingletonStore<P> {
private final Injector injector;
private final Class<P> parentClass;
SingletonStoreImpl(Class<P> parentClass, Injector injector) {
this.parentClass = parentClass;
this.injector = injector;
}
@Override
public <C extends P> C getSingleton(Class<C> clazz) {
if (parentClass.isAssignableFrom(clazz)) {
return injector.getSingleton(clazz);
}
throw new IllegalArgumentException(clazz + " not child of " + parentClass);
}
@Override
public Collection<P> retrieveAllOfType() {
return retrieveAllOfType(parentClass);
}
@Override
public <C extends P> Collection<C> retrieveAllOfType(Class<C> clazz) {
if (parentClass.isAssignableFrom(clazz)) {
return injector.retrieveAllOfType(clazz);
}
throw new IllegalArgumentException(clazz + " not child of " + parentClass);
}
}
}

View File

@ -29,7 +29,7 @@ import java.util.regex.Pattern;
/**
* Service for performing various verifications when a player joins.
*/
class OnJoinVerifier implements Reloadable {
public class OnJoinVerifier implements Reloadable {
@Inject
private Settings settings;
@ -111,7 +111,7 @@ class OnJoinVerifier implements Reloadable {
* @param event the login event to verify
*
* @return true if the player's connection should be refused (i.e. the event does not need to be processed
* further), false if the player is not refused
* further), false if the player is not refused
*/
public boolean refusePlayerForFullServer(PlayerLoginEvent event) {
final Player player = event.getPlayer();

View File

@ -7,6 +7,7 @@ import fr.xephi.authme.message.Messages;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.service.AntiBotService;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.JoinMessageService;
import fr.xephi.authme.service.TeleportationService;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.settings.Settings;
@ -54,8 +55,6 @@ import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOW_UNAU
*/
public class PlayerListener implements Listener {
public static final Map<String, String> joinMessage = new ConcurrentHashMap<>();
@Inject
private Settings settings;
@Inject
@ -78,6 +77,8 @@ public class PlayerListener implements Listener {
private TeleportationService teleportationService;
@Inject
private ValidationService validationService;
@Inject
private JoinMessageService joinMessageService;
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
@ -175,7 +176,7 @@ public class PlayerListener implements Listener {
String customJoinMessage = settings.getProperty(RegistrationSettings.CUSTOM_JOIN_MESSAGE);
if (!customJoinMessage.isEmpty()) {
event.setJoinMessage(customJoinMessage.replace("{PLAYERNAME}", player.getName())
.replace("{DISPLAYNAME]", player.getDisplayName()));
.replace("{DISPLAYNAME}", player.getDisplayName()));
}
if (!settings.getProperty(RegistrationSettings.DELAY_JOIN_MESSAGE)) {
@ -188,7 +189,7 @@ public class PlayerListener implements Listener {
// Remove the join message while the player isn't logging in
if (joinMsg != null) {
event.setJoinMessage(null);
joinMessage.put(name, joinMsg);
joinMessageService.putMessage(name, joinMsg);
}
}

View File

@ -14,6 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.xephi.authme.listener.protocollib;
import com.comphenix.protocol.PacketType;
@ -46,7 +47,7 @@ class InventoryPacketAdapter extends PacketAdapter {
private static final int MAIN_SIZE = 27;
private static final int HOTBAR_SIZE = 9;
public InventoryPacketAdapter(AuthMe plugin) {
InventoryPacketAdapter(AuthMe plugin) {
super(plugin, PacketType.Play.Server.SET_SLOT, PacketType.Play.Server.WINDOW_ITEMS);
}

View File

@ -12,7 +12,7 @@ import fr.xephi.authme.data.auth.PlayerCache;
class TabCompletePacketAdapter extends PacketAdapter {
public TabCompletePacketAdapter(AuthMe plugin) {
TabCompletePacketAdapter(AuthMe plugin) {
super(plugin, ListenerPriority.NORMAL, PacketType.Play.Client.TAB_COMPLETE);
}

View File

@ -0,0 +1,125 @@
package fr.xephi.authme.mail;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.FileUtils;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.bukkit.Server;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
/**
* Creates emails and sends them.
*/
public class EmailService {
private final File dataFolder;
private final String serverName;
private final Settings settings;
private final SendMailSsl sendMailSsl;
@Inject
EmailService(@DataFolder File dataFolder, Server server, Settings settings, SendMailSsl sendMailSsl) {
this.dataFolder = dataFolder;
this.serverName = server.getServerName();
this.settings = settings;
this.sendMailSsl = sendMailSsl;
}
public boolean hasAllInformation() {
return sendMailSsl.hasAllInformation();
}
/**
* Sends an email to the user with his new password.
*
* @param name the name of the player
* @param mailAddress the player's email
* @param newPass the new password
* @return true if email could be sent, false otherwise
*/
public boolean sendPasswordMail(String name, String mailAddress, String newPass) {
if (!hasAllInformation()) {
ConsoleLogger.warning("Cannot perform email registration: not all email settings are complete");
return false;
}
HtmlEmail email;
try {
email = sendMailSsl.initializeMail(mailAddress);
} catch (EmailException e) {
ConsoleLogger.logException("Failed to create email with the given settings:", e);
return false;
}
String mailText = replaceTagsForPasswordMail(settings.getPasswordEmailMessage(), name, newPass);
// Generate an image?
File file = null;
if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) {
try {
file = generateImage(name, newPass);
mailText = embedImageIntoEmailContent(file, email, mailText);
} catch (IOException | EmailException e) {
ConsoleLogger.logException(
"Unable to send new password as image for email " + mailAddress + ":", e);
}
}
boolean couldSendEmail = sendMailSsl.sendEmail(mailText, email);
FileUtils.delete(file);
return couldSendEmail;
}
public boolean sendRecoveryCode(String name, String email, String code) {
HtmlEmail htmlEmail;
try {
htmlEmail = sendMailSsl.initializeMail(email);
} catch (EmailException e) {
ConsoleLogger.logException("Failed to create email for recovery code:", e);
return false;
}
String message = replaceTagsForRecoveryCodeMail(settings.getRecoveryCodeEmailMessage(),
name, code, settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID));
return sendMailSsl.sendEmail(message, htmlEmail);
}
private File generateImage(String name, String newPass) throws IOException {
ImageGenerator gen = new ImageGenerator(newPass);
File file = new File(dataFolder, name + "_new_pass.jpg");
ImageIO.write(gen.generateImage(), "jpg", file);
return file;
}
private static String embedImageIntoEmailContent(File image, HtmlEmail email, String content)
throws EmailException {
DataSource source = new FileDataSource(image);
String tag = email.embed(source, image.getName());
return content.replace("<image />", "<img src=\"cid:" + tag + "\">");
}
private String replaceTagsForPasswordMail(String mailText, String name, String newPass) {
return mailText
.replace("<playername />", name)
.replace("<servername />", serverName)
.replace("<generatedpass />", newPass);
}
private String replaceTagsForRecoveryCodeMail(String mailText, String name, String code, int hoursValid) {
return mailText
.replace("<playername />", name)
.replace("<servername />", serverName)
.replace("<recoverycode />", code)
.replace("<hoursvalid />", String.valueOf(hoursValid));
}
}

View File

@ -1,27 +1,19 @@
package fr.xephi.authme.mail;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.FileUtils;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.util.StringUtils;
import org.apache.commons.mail.EmailConstants;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.bukkit.Server;
import javax.activation.CommandMap;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.activation.MailcapCommandMap;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import javax.mail.Session;
import java.io.File;
import java.io.IOException;
import java.security.Security;
import java.util.Properties;
@ -32,18 +24,10 @@ import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD;
/**
* Sends emails to players on behalf of the server.
*/
public class SendMailSSL {
private final File dataFolder;
private final String serverName;
private final Settings settings;
public class SendMailSsl {
@Inject
SendMailSSL(@DataFolder File dataFolder, Server server, Settings settings) {
this.dataFolder = dataFolder;
this.serverName = server.getServerName();
this.settings = settings;
}
private Settings settings;
/**
* Returns whether all necessary settings are set for sending mails.
@ -55,76 +39,7 @@ public class SendMailSSL {
&& !settings.getProperty(MAIL_PASSWORD).isEmpty();
}
/**
* Sends an email to the user with his new password.
*
* @param name the name of the player
* @param mailAddress the player's email
* @param newPass the new password
* @return true if email could be sent, false otherwise
*/
public boolean sendPasswordMail(String name, String mailAddress, String newPass) {
if (!hasAllInformation()) {
ConsoleLogger.warning("Cannot perform email registration: not all email settings are complete");
return false;
}
HtmlEmail email;
try {
email = initializeMail(mailAddress);
} catch (EmailException e) {
ConsoleLogger.logException("Failed to create email with the given settings:", e);
return false;
}
String mailText = replaceTagsForPasswordMail(settings.getPasswordEmailMessage(), name, newPass);
// Generate an image?
File file = null;
if (settings.getProperty(EmailSettings.PASSWORD_AS_IMAGE)) {
try {
file = generateImage(name, newPass);
mailText = embedImageIntoEmailContent(file, email, mailText);
} catch (IOException | EmailException e) {
ConsoleLogger.logException(
"Unable to send new password as image for email " + mailAddress + ":", e);
}
}
boolean couldSendEmail = sendEmail(mailText, email);
FileUtils.delete(file);
return couldSendEmail;
}
public boolean sendRecoveryCode(String name, String email, String code) {
HtmlEmail htmlEmail;
try {
htmlEmail = initializeMail(email);
} catch (EmailException e) {
ConsoleLogger.logException("Failed to create email for recovery code:", e);
return false;
}
String message = replaceTagsForRecoveryCodeMail(settings.getRecoveryCodeEmailMessage(),
name, code, settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID));
return sendEmail(message, htmlEmail);
}
private File generateImage(String name, String newPass) throws IOException {
ImageGenerator gen = new ImageGenerator(newPass);
File file = new File(dataFolder, name + "_new_pass.jpg");
ImageIO.write(gen.generateImage(), "jpg", file);
return file;
}
private static String embedImageIntoEmailContent(File image, HtmlEmail email, String content)
throws EmailException {
DataSource source = new FileDataSource(image);
String tag = email.embed(source, image.getName());
return content.replace("<image />", "<img src=\"cid:" + tag + "\">");
}
@VisibleForTesting
HtmlEmail initializeMail(String emailAddress) throws EmailException {
public HtmlEmail initializeMail(String emailAddress) throws EmailException {
String senderMail = StringUtils.isEmpty(settings.getProperty(EmailSettings.MAIL_ADDRESS))
? settings.getProperty(EmailSettings.MAIL_ACCOUNT)
: settings.getProperty(EmailSettings.MAIL_ADDRESS);
@ -143,14 +58,16 @@ public class SendMailSSL {
email.setFrom(senderMail, senderName);
email.setSubject(settings.getProperty(EmailSettings.RECOVERY_MAIL_SUBJECT));
email.setAuthentication(settings.getProperty(EmailSettings.MAIL_ACCOUNT), mailPassword);
if (settings.getProperty(PluginSettings.LOG_LEVEL).includes(LogLevel.DEBUG)) {
email.setDebug(true);
}
setPropertiesForPort(email, port);
return email;
}
@VisibleForTesting
boolean sendEmail(String content, HtmlEmail email) {
Thread.currentThread().setContextClassLoader(SendMailSSL.class.getClassLoader());
public boolean sendEmail(String content, HtmlEmail email) {
Thread.currentThread().setContextClassLoader(SendMailSsl.class.getClassLoader());
// Issue #999: Prevent UnsupportedDataTypeException: no object DCH for MIME type multipart/alternative
// cf. http://stackoverflow.com/questions/21856211/unsupporteddatatypeexception-no-object-dch-for-mime-type
MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
@ -176,21 +93,6 @@ public class SendMailSSL {
}
}
private String replaceTagsForPasswordMail(String mailText, String name, String newPass) {
return mailText
.replace("<playername />", name)
.replace("<servername />", serverName)
.replace("<generatedpass />", newPass);
}
private String replaceTagsForRecoveryCodeMail(String mailText, String name, String code, int hoursValid) {
return mailText
.replace("<playername />", name)
.replace("<servername />", serverName)
.replace("<recoverycode />", code)
.replace("<hoursvalid />", String.valueOf(hoursValid));
}
private void setPropertiesForPort(HtmlEmail email, int port) throws EmailException {
switch (port) {
case 587:
@ -214,8 +116,10 @@ public class SendMailSSL {
}
break;
case 25:
email.setStartTLSEnabled(true);
email.setSSLCheckServerIdentity(true);
if (settings.getProperty(EmailSettings.PORT25_USE_TLS)) {
email.setStartTLSEnabled(true);
email.setSSLCheckServerIdentity(true);
}
break;
case 465:
email.setSslSmtpPort(Integer.toString(port));

View File

@ -1,6 +1,7 @@
package fr.xephi.authme.message;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.util.FileUtils;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
@ -16,6 +17,7 @@ public class MessageFileHandler {
// regular file
private final String filename;
private final FileConfiguration configuration;
private final String updateAddition;
// default file
private final String defaultFile;
private FileConfiguration defaultConfiguration;
@ -25,11 +27,15 @@ public class MessageFileHandler {
*
* @param file the file to use for messages
* @param defaultFile the default file from the JAR to use if no message is found
* @param updateCommand command to update the messages file (nullable) to show in error messages
*/
public MessageFileHandler(File file, String defaultFile) {
public MessageFileHandler(File file, String defaultFile, String updateCommand) {
this.filename = file.getName();
this.configuration = YamlConfiguration.loadConfiguration(file);
this.defaultFile = defaultFile;
this.updateAddition = updateCommand == null
? ""
: " (or run " + updateCommand + ")";
}
/**
@ -53,7 +59,7 @@ public class MessageFileHandler {
if (message == null) {
ConsoleLogger.warning("Error getting message with key '" + key + "'. "
+ "Please update your config file '" + filename + "' (or run /authme messages)");
+ "Please update your config file '" + filename + "'" + updateAddition);
return getDefault(key);
}
return message;
@ -78,7 +84,7 @@ public class MessageFileHandler {
*/
private String getDefault(String key) {
if (defaultConfiguration == null) {
InputStream stream = MessageFileHandler.class.getClassLoader().getResourceAsStream(defaultFile);
InputStream stream = FileUtils.getResourceFromJar(defaultFile);
defaultConfiguration = YamlConfiguration.loadConfiguration(new InputStreamReader(stream));
}
String message = defaultConfiguration.getString(key);

View File

@ -36,10 +36,23 @@ public class MessageFileHandlerProvider {
* @return the message file handler
*/
public MessageFileHandler initializeHandler(Function<String, String> pathBuilder) {
return initializeHandler(pathBuilder, null);
}
/**
* Initializes a message file handler with the messages file of the configured language.
* Ensures beforehand that the messages file exists or creates it otherwise.
*
* @param pathBuilder function taking the configured language code as argument and returning the messages file
* @param updateCommand command to run to update the languages file (nullable)
* @return the message file handler
*/
public MessageFileHandler initializeHandler(Function<String, String> pathBuilder, String updateCommand) {
String language = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
return new MessageFileHandler(
initializeFile(language, pathBuilder),
pathBuilder.apply(DEFAULT_LANGUAGE));
pathBuilder.apply(DEFAULT_LANGUAGE),
updateCommand);
}
/**
@ -53,7 +66,8 @@ public class MessageFileHandlerProvider {
File initializeFile(String language, Function<String, String> pathBuilder) {
String filePath = pathBuilder.apply(language);
File file = new File(dataFolder, filePath);
if (FileUtils.copyFileFromResource(file, filePath)) {
// Check that JAR file exists to avoid logging an error
if (FileUtils.getResourceFromJar(filePath) != null && FileUtils.copyFileFromResource(file, filePath)) {
return file;
}

View File

@ -17,16 +17,13 @@ public enum MessageKey {
/** AntiBot protection mode is enabled! You have to wait some minutes before joining the server. */
KICK_ANTIBOT("kick_antibot"),
/** Can't find the requested user in the database! */
/** This user isn't registered! */
UNKNOWN_USER("unknown_user"),
/** Your quit location was unsafe, you have been teleported to the world's spawnpoint. */
UNSAFE_QUIT_LOCATION("unsafe_spawn"),
/** You're not logged in! */
NOT_LOGGED_IN("not_logged_in"),
/** Usage: /login &lt;password> */
/** Usage: /login &lt;password&gt; */
USAGE_LOGIN("usage_log"),
/** Wrong password! */
@ -56,19 +53,19 @@ public enum MessageKey {
/** An unexpected error occurred, please contact an administrator! */
ERROR("error"),
/** Please, login with the command "/login &lt;password>" */
/** Please, login with the command: /login &lt;password&gt; */
LOGIN_MESSAGE("login_msg"),
/** Please, register to the server with the command "/register &lt;password> &lt;ConfirmPassword>" */
/** Please, register to the server with the command: /register &lt;password&gt; &lt;ConfirmPassword&gt; */
REGISTER_MESSAGE("reg_msg"),
/** You have exceeded the maximum number of registrations (%reg_count/%max_acc %reg_names) for your connection! */
MAX_REGISTER_EXCEEDED("max_reg", "%max_acc", "%reg_count", "%reg_names"),
/** Usage: /register &lt;password> &lt;ConfirmPassword> */
/** Usage: /register &lt;password&gt; &lt;ConfirmPassword&gt; */
USAGE_REGISTER("usage_reg"),
/** Usage: /unregister &lt;password> */
/** Usage: /unregister &lt;password&gt; */
USAGE_UNREGISTER("usage_unreg"),
/** Password changed successfully! */
@ -95,7 +92,7 @@ public enum MessageKey {
/** You're already logged in! */
ALREADY_LOGGED_IN_ERROR("logged_in"),
/** Logged-out successfully! */
/** Logged out successfully! */
LOGOUT_SUCCESS("logout"),
/** The same username is already playing on the server! */
@ -113,7 +110,7 @@ public enum MessageKey {
/** Login timeout exceeded, you have been kicked from the server, please try again! */
LOGIN_TIMEOUT_ERROR("timeout"),
/** Usage: /changepassword &lt;oldPassword> &lt;newPassword> */
/** Usage: /changepassword &lt;oldPassword&gt; &lt;newPassword&gt; */
USAGE_CHANGE_PASSWORD("usage_changepassword"),
/** Your username is either too short or too long! */
@ -122,13 +119,13 @@ public enum MessageKey {
/** Your username contains illegal characters. Allowed chars: REG_EX */
INVALID_NAME_CHARACTERS("regex", "REG_EX"),
/** Please add your email to your account with the command "/email add &lt;yourEmail> &lt;confirmEmail>" */
/** Please add your email to your account with the command: /email add &lt;yourEmail&gt; &lt;confirmEmail&gt; */
ADD_EMAIL_MESSAGE("add_email"),
/** Forgot your password? Please use the command "/email recovery &lt;yourEmail>" */
/** Forgot your password? Please use the command: /email recovery &lt;yourEmail&gt; */
FORGOT_PASSWORD_MESSAGE("recovery_email"),
/** To login you have to solve a captcha code, please use the command "/captcha &lt;theCaptcha>" */
/** To login you have to solve a captcha code, please use the command: /captcha &lt;theCaptcha&gt; */
USAGE_CAPTCHA("usage_captcha", "<theCaptcha>"),
/** Wrong captcha, please type "/captcha THE_CAPTCHA" into the chat! */
@ -143,13 +140,13 @@ public enum MessageKey {
/** The server is full, try again later! */
KICK_FULL_SERVER("kick_fullserver"),
/** Usage: /email add &lt;email> &lt;confirmEmail> */
/** Usage: /email add &lt;email&gt; &lt;confirmEmail&gt; */
USAGE_ADD_EMAIL("usage_email_add"),
/** Usage: /email change &lt;oldEmail> &lt;newEmail> */
/** Usage: /email change &lt;oldEmail&gt; &lt;newEmail&gt; */
USAGE_CHANGE_EMAIL("usage_email_change"),
/** Usage: /email recovery &lt;Email> */
/** Usage: /email recovery &lt;Email&gt; */
USAGE_RECOVER_EMAIL("usage_email_recovery"),
/** Invalid new email, try again! */
@ -179,9 +176,6 @@ public enum MessageKey {
/** Recovery email sent successfully! Please check your email inbox! */
RECOVERY_EMAIL_SENT_MESSAGE("email_send"),
/** A recovery email was already sent! You can discard it and send a new one using the command below: */
RECOVERY_EMAIL_ALREADY_SENT_MESSAGE("email_exists"),
/** Your country is banned from this server! */
COUNTRY_BANNED_ERROR("country_banned"),
@ -224,8 +218,51 @@ public enum MessageKey {
/** A recovery code to reset your password has been sent to your email. */
RECOVERY_CODE_SENT("recovery_code_sent"),
/** The recovery code is not correct! Use /email recovery [email] to generate a new one */
INCORRECT_RECOVERY_CODE("recovery_code_incorrect");
/** The recovery code is not correct! You have %count tries remaining. */
INCORRECT_RECOVERY_CODE("recovery_code_incorrect", "%count"),
/**
* You have exceeded the maximum number of attempts to enter the recovery code.
* Use "/email recovery [email]" to generate a new one.
*/
RECOVERY_TRIES_EXCEEDED("recovery_tries_exceeded"),
/** Recovery code entered correctly! */
RECOVERY_CODE_CORRECT("recovery_code_correct"),
/** Please use the command /email setpassword to change your password immediately. */
RECOVERY_CHANGE_PASSWORD("recovery_change_password"),
/** You cannot change your password using this command anymore. */
CHANGE_PASSWORD_EXPIRED("change_password_expired"),
/** An email was already sent recently. You must wait %time before you can send a new one. */
EMAIL_COOLDOWN_ERROR("email_cooldown_error", "%time"),
/** second */
SECOND("second"),
/** seconds */
SECONDS("seconds"),
/** minute */
MINUTE("minute"),
/** minutes */
MINUTES("minutes"),
/** hour */
HOUR("hour"),
/** hours */
HOURS("hours"),
/** day */
DAY("day"),
/** days */
DAYS("days");
private String key;
private String[] tags;

View File

@ -1,11 +1,15 @@
package fr.xephi.authme.message;
import com.google.common.collect.ImmutableMap;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.util.expiring.Duration;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Class for retrieving and sending translatable messages to players.
@ -15,6 +19,20 @@ public class Messages implements Reloadable {
// Custom Authme tag replaced to new line
private static final String NEWLINE_TAG = "%nl%";
/** Contains the keys of the singular messages for time units. */
private static final Map<TimeUnit, MessageKey> TIME_UNIT_SINGULARS = ImmutableMap.<TimeUnit, MessageKey>builder()
.put(TimeUnit.SECONDS, MessageKey.SECOND)
.put(TimeUnit.MINUTES, MessageKey.MINUTE)
.put(TimeUnit.HOURS, MessageKey.HOUR)
.put(TimeUnit.DAYS, MessageKey.DAY).build();
/** Contains the keys of the plural messages for time units. */
private static final Map<TimeUnit, MessageKey> TIME_UNIT_PLURALS = ImmutableMap.<TimeUnit, MessageKey>builder()
.put(TimeUnit.SECONDS, MessageKey.SECONDS)
.put(TimeUnit.MINUTES, MessageKey.MINUTES)
.put(TimeUnit.HOURS, MessageKey.HOURS)
.put(TimeUnit.DAYS, MessageKey.DAYS).build();
private final MessageFileHandlerProvider messageFileHandlerProvider;
private MessageFileHandler messageFileHandler;
@ -71,6 +89,22 @@ public class Messages implements Reloadable {
return message.split("\n");
}
/**
* Returns the textual representation for the given duration.
* Note that this class only supports the time units days, hour, minutes and seconds.
*
* @param duration the duration to build a text of
* @return text of the duration
*/
public String formatDuration(Duration duration) {
long value = duration.getDuration();
MessageKey timeUnitKey = value == 1
? TIME_UNIT_SINGULARS.get(duration.getTimeUnit())
: TIME_UNIT_PLURALS.get(duration.getTimeUnit());
return value + " " + retrieveMessage(timeUnitKey);
}
/**
* Retrieve the message from the text file.
*
@ -107,7 +141,7 @@ public class Messages implements Reloadable {
@Override
public void reload() {
this.messageFileHandler = messageFileHandlerProvider
.initializeHandler(lang -> "messages/messages_" + lang + ".yml");
.initializeHandler(lang -> "messages/messages_" + lang + ".yml", "/authme messages");
}
private static String formatMessage(String message) {

View File

@ -1,17 +1,24 @@
package fr.xephi.authme.output;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.util.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Service class for the log filters.
*/
public final class LogFilterHelper {
final class LogFilterHelper {
private static final String ISSUED_COMMAND_TEXT = "issued server command:";
private static final String[] COMMANDS_TO_SKIP = {"/login ", "/l ", "/reg ", "/changepassword ",
"/unregister ", "/authme register ", "/authme changepassword ", "/authme reg ", "/authme cp ",
"/register "};
@VisibleForTesting
static final List<String> COMMANDS_TO_SKIP = withAndWithoutAuthMePrefix(
"/login ", "/l ", "/log ", "/register ", "/reg ", "/unregister ", "/unreg ",
"/changepassword ", "/cp ", "/changepass ", "/authme register ", "/authme reg ", "/authme r ",
"/authme changepassword ", "/authme password ", "/authme changepass ", "/authme cp ");
private LogFilterHelper() {
// Util class
@ -24,11 +31,20 @@ public final class LogFilterHelper {
*
* @return True if it is a sensitive AuthMe command, false otherwise
*/
public static boolean isSensitiveAuthMeCommand(String message) {
static boolean isSensitiveAuthMeCommand(String message) {
if (message == null) {
return false;
}
String lowerMessage = message.toLowerCase();
return lowerMessage.contains(ISSUED_COMMAND_TEXT) && StringUtils.containsAny(lowerMessage, COMMANDS_TO_SKIP);
}
private static List<String> withAndWithoutAuthMePrefix(String... commands) {
List<String> commandList = new ArrayList<>(commands.length * 2);
for (String command : commands) {
commandList.add(command);
commandList.add(command.substring(0, 1) + "authme:" + command.substring(1));
}
return Collections.unmodifiableList(commandList);
}
}

View File

@ -1,21 +1,28 @@
package fr.xephi.authme.permission;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.HooksSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import org.bukkit.entity.Player;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.Optional;
/**
* Changes the permission group according to the auth status of the player and the configuration.
* <p>
* If this feature is enabled, the <i>primary permissions group</i> of a player is replaced until he has
* logged in. Some permission plugins have a notion of a primary group; for other permission plugins the
* first group is simply taken.
* <p>
* The groups that are used as replacement until the player logs in is configurable and depends on if
* the player is registered or not. Note that some (all?) permission systems require the group to actually
* exist for the replacement to take place. Furthermore, since some permission groups require that players
* be in at least one group, this will mean that the player is not removed from his primary group.
*/
public class AuthGroupHandler implements Reloadable {
@ -26,9 +33,8 @@ public class AuthGroupHandler implements Reloadable {
private Settings settings;
@Inject
private LimboCache limboCache;
private LimboService limboService;
private String unloggedInGroup;
private String unregisteredGroup;
private String registeredGroup;
@ -36,15 +42,53 @@ public class AuthGroupHandler implements Reloadable {
}
/**
* Set the group of a player, by its AuthMe group type.
* Sets the group of a player by its authentication status.
*
* @param player The player.
* @param group The group type.
*
* @return True if succeeded, false otherwise. False is also returned if groups aren't supported
* with the current permissions system.
* @param player the player
* @param groupType the group type
*/
public boolean setGroup(Player player, AuthGroupType group) {
public void setGroup(Player player, AuthGroupType groupType) {
if (!useAuthGroups()) {
return;
}
String primaryGroup = Optional
.ofNullable(limboService.getLimboPlayer(player.getName()))
.map(LimboPlayer::getGroup)
.orElse("");
switch (groupType) {
// Implementation note: some permission systems don't support players not being in any group,
// so add the new group before removing the old ones
case UNREGISTERED:
permissionsManager.addGroup(player, unregisteredGroup);
permissionsManager.removeGroups(player, registeredGroup, primaryGroup);
break;
case REGISTERED_UNAUTHENTICATED:
permissionsManager.addGroup(player, registeredGroup);
permissionsManager.removeGroups(player, unregisteredGroup, primaryGroup);
break;
case LOGGED_IN:
permissionsManager.addGroup(player, primaryGroup);
permissionsManager.removeGroups(player, unregisteredGroup, registeredGroup);
break;
default:
throw new IllegalStateException("Encountered unhandled auth group type '" + groupType + "'");
}
ConsoleLogger.debug(() -> player.getName() + " changed to "
+ groupType + ": has groups " + permissionsManager.getGroups(player));
}
/**
* Returns whether the auth permissions group function should be used.
*
* @return true if should be used, false otherwise
*/
private boolean useAuthGroups() {
// Check whether the permissions check is enabled
if (!settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) {
return false;
@ -55,72 +99,14 @@ public class AuthGroupHandler implements Reloadable {
ConsoleLogger.warning("The current permissions system doesn't have group support, unable to set group!");
return false;
}
switch (group) {
case UNREGISTERED:
// Remove the other group type groups, set the current group
permissionsManager.removeGroups(player, Arrays.asList(registeredGroup, unloggedInGroup));
return permissionsManager.addGroup(player, unregisteredGroup);
case REGISTERED:
// Remove the other group type groups, set the current group
permissionsManager.removeGroups(player, Arrays.asList(unregisteredGroup, unloggedInGroup));
return permissionsManager.addGroup(player, registeredGroup);
case NOT_LOGGED_IN:
// Remove the other group type groups, set the current group
permissionsManager.removeGroups(player, Arrays.asList(unregisteredGroup, registeredGroup));
return permissionsManager.addGroup(player, unloggedInGroup);
case LOGGED_IN:
// Get the player data
LimboPlayer data = limboCache.getPlayerData(player.getName().toLowerCase());
if (data == null) {
return false;
}
// Get the players group
String realGroup = data.getGroup();
// Remove the other group types groups, set the real group
permissionsManager.removeGroups(player,
Arrays.asList(unregisteredGroup, registeredGroup, unloggedInGroup)
);
return permissionsManager.addGroup(player, realGroup);
default:
return false;
}
}
/**
* TODO: This method requires better explanation.
* <p>
* Set the normal group of a player.
*
* @param player The player.
* @param group The normal group.
*
* @return True on success, false on failure.
*/
public boolean addNormal(Player player, String group) {
// Check whether the permissions check is enabled
if (!settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) {
return false;
}
// Remove old groups
permissionsManager.removeGroups(player, Arrays.asList(unregisteredGroup, registeredGroup, unloggedInGroup));
// Add the normal group, return the result
return permissionsManager.addGroup(player, group);
return true;
}
@Override
@PostConstruct
public void reload() {
unloggedInGroup = settings.getProperty(SecuritySettings.UNLOGGEDIN_GROUP);
unregisteredGroup = settings.getProperty(HooksSettings.UNREGISTERED_GROUP);
registeredGroup = settings.getProperty(HooksSettings.REGISTERED_GROUP);
unregisteredGroup = settings.getProperty(PluginSettings.UNREGISTERED_GROUP);
registeredGroup = settings.getProperty(PluginSettings.REGISTERED_GROUP);
}
}

View File

@ -8,11 +8,8 @@ public enum AuthGroupType {
/** Player does not have an account. */
UNREGISTERED,
/** Registered? */
REGISTERED, // TODO #761: Remove this or the NOT_LOGGED_IN one
/** Player is registered and not logged in. */
NOT_LOGGED_IN,
/** Player is registered but not logged in. */
REGISTERED_UNAUTHENTICATED,
/** Player is logged in. */
LOGGED_IN

View File

@ -19,8 +19,8 @@ import org.bukkit.plugin.PluginManager;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.Collections;
/**
* <p>
@ -74,7 +74,7 @@ public class PermissionsManager implements Reloadable {
// Loop through all the available permissions system types
for (PermissionsSystemType type : PermissionsSystemType.values()) {
try {
PermissionHandler handler = getPermissionHandler(type);
PermissionHandler handler = createPermissionHandler(type);
if (handler != null) {
// Show a success message and return
this.handler = handler;
@ -91,7 +91,14 @@ public class PermissionsManager implements Reloadable {
ConsoleLogger.info("No supported permissions system found! Permissions are disabled!");
}
private PermissionHandler getPermissionHandler(PermissionsSystemType type) throws PermissionHandlerException {
/**
* Creates a permission handler for the provided permission systems if possible.
*
* @param type the permission systems type for which to create a corresponding permission handler
* @return the permission handler, or {@code null} if not possible
* @throws PermissionHandlerException during initialization of the permission handler
*/
private PermissionHandler createPermissionHandler(PermissionsSystemType type) throws PermissionHandlerException {
// Try to find the plugin for the current permissions system
Plugin plugin = pluginManager.getPlugin(type.getPluginName());
@ -255,12 +262,12 @@ public class PermissionsManager implements Reloadable {
*
* @param player The player.
*
* @return Permission groups, or an empty list if this feature is not supported.
* @return Permission groups, or an empty collection if this feature is not supported.
*/
public List<String> getGroups(Player player) {
public Collection<String> getGroups(Player player) {
// If no permissions system is used, return an empty list
if (!isEnabled())
return new ArrayList<>();
return Collections.emptyList();
return handler.getGroups(player);
}
@ -289,7 +296,7 @@ public class PermissionsManager implements Reloadable {
* @return True if the player is in the specified group, false otherwise.
* False is also returned if groups aren't supported by the used permissions system.
*/
public boolean inGroup(Player player, String groupName) {
public boolean isInGroup(Player player, String groupName) {
// If no permissions system is used, return false
if (!isEnabled())
return false;
@ -307,42 +314,12 @@ public class PermissionsManager implements Reloadable {
* False is also returned if this feature isn't supported for the current permissions system.
*/
public boolean addGroup(Player player, String groupName) {
if (StringUtils.isEmpty(groupName)) {
if (!isEnabled() || StringUtils.isEmpty(groupName)) {
return false;
}
// If no permissions system is used, return false
if (!isEnabled()) {
return false;
}
return handler.addToGroup(player, groupName);
}
/**
* Add the permission groups of a player, if supported.
*
* @param player The player
* @param groupNames The name of the groups to add.
*
* @return True if succeed, false otherwise.
* False is also returned if this feature isn't supported for the current permissions system.
*/
public boolean addGroups(Player player, List<String> groupNames) {
// If no permissions system is used, return false
if (!isEnabled())
return false;
// Add each group to the user
boolean result = true;
for (String groupName : groupNames)
if (!addGroup(player, groupName))
result = false;
// Return the result
return result;
}
/**
* Remove the permission group of a player, if supported.
*
@ -352,8 +329,7 @@ public class PermissionsManager implements Reloadable {
* @return True if succeed, false otherwise.
* False is also returned if this feature isn't supported for the current permissions system.
*/
public boolean removeGroup(Player player, String groupName) {
// If no permissions system is used, return false
public boolean removeGroups(Player player, String groupName) {
if (!isEnabled())
return false;
@ -369,16 +345,18 @@ public class PermissionsManager implements Reloadable {
* @return True if succeed, false otherwise.
* False is also returned if this feature isn't supported for the current permissions system.
*/
public boolean removeGroups(Player player, List<String> groupNames) {
public boolean removeGroups(Player player, String... groupNames) {
// If no permissions system is used, return false
if (!isEnabled())
return false;
// Add each group to the user
boolean result = true;
for (String groupName : groupNames)
if (!removeGroup(player, groupName))
for (String groupName : groupNames) {
if (!handler.removeFromGroup(player, groupName)) {
result = false;
}
}
// Return the result
return result;
@ -402,41 +380,6 @@ public class PermissionsManager implements Reloadable {
return handler.setGroup(player, groupName);
}
/**
* Set the permission groups of a player, if supported.
* This clears the current groups of the player.
*
* @param player The player
* @param groupNames The name of the groups to set.
*
* @return True if succeed, false otherwise.
* False is also returned if this feature isn't supported for the current permissions system.
*/
public boolean setGroups(Player player, List<String> groupNames) {
// If no permissions system is used or if there's no group supplied, return false
if (!isEnabled() || groupNames.isEmpty())
return false;
// Set the main group
if (!setGroup(player, groupNames.get(0)))
return false;
// Add the rest of the groups
boolean result = true;
for (int i = 1; i < groupNames.size(); i++) {
// Get the group name
String groupName = groupNames.get(i);
// Add this group
if (!addGroup(player, groupName)) {
result = false;
}
}
// Return the result
return result;
}
/**
* Remove all groups of the specified player, if supported.
* Systems like Essentials GroupManager don't allow all groups to be removed from a player, thus the user will stay
@ -453,9 +396,9 @@ public class PermissionsManager implements Reloadable {
return false;
// Get a list of current groups
List<String> groupNames = getGroups(player);
Collection<String> groupNames = getGroups(player);
// Remove each group
return removeGroups(player, groupNames);
return removeGroups(player, groupNames.toArray(new String[groupNames.size()]));
}
}

View File

@ -29,7 +29,12 @@ public enum PlayerStatePermission implements PermissionNode {
/**
* Permission to bypass the purging process.
*/
BYPASS_PURGE("authme.bypasspurge", DefaultPermission.NOT_ALLOWED);
BYPASS_PURGE("authme.bypasspurge", DefaultPermission.NOT_ALLOWED),
/**
* Permission to use the /authme debug command.
*/
DEBUG_COMMAND("authme.debug", DefaultPermission.OP_ONLY);
/**
* The permission node.

View File

@ -9,6 +9,12 @@ import org.bukkit.entity.Player;
import java.util.Arrays;
import java.util.List;
/**
* Handler for bPermissions.
*
* @see <a href="https://dev.bukkit.org/projects/bpermissions">bPermissions Bukkit page</a>
* @see <a href="https://github.com/rymate1234/bPermissions/">bPermissions on Github</a>
*/
public class BPermissionsHandler implements PermissionHandler {
@Override
@ -49,19 +55,6 @@ public class BPermissionsHandler implements PermissionHandler {
return Arrays.asList(ApiLayer.getGroups(player.getWorld().getName(), CalculableType.USER, player.getName()));
}
@Override
public String getPrimaryGroup(Player player) {
// Get the groups of the player
List<String> groups = getGroups(player);
// Make sure there is any group available, or return null
if (groups.isEmpty())
return null;
// Return the first group
return groups.get(0);
}
@Override
public PermissionsSystemType getPermissionSystem() {
return PermissionsSystemType.B_PERMISSIONS;

View File

@ -2,9 +2,10 @@ package fr.xephi.authme.permission.handlers;
import fr.xephi.authme.permission.PermissionNode;
import fr.xephi.authme.permission.PermissionsSystemType;
import fr.xephi.authme.util.Utils;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.Collection;
public interface PermissionHandler {
@ -16,7 +17,7 @@ public interface PermissionHandler {
* @param group The name of the group.
*
* @return True if succeed, false otherwise.
* False is also returned if this feature isn't supported for the current permissions system.
* False is also returned if this feature isn't supported for the current permissions system.
*/
boolean addToGroup(Player player, String group);
@ -46,9 +47,11 @@ public interface PermissionHandler {
* @param group The group name.
*
* @return True if the player is in the specified group, false otherwise.
* False is also returned if groups aren't supported by the used permissions system.
* False is also returned if groups aren't supported by the used permissions system.
*/
boolean isInGroup(Player player, String group);
default boolean isInGroup(Player player, String group) {
return getGroups(player).contains(group);
}
/**
* Remove the permission group of a player, if supported.
@ -57,7 +60,7 @@ public interface PermissionHandler {
* @param group The name of the group.
*
* @return True if succeed, false otherwise.
* False is also returned if this feature isn't supported for the current permissions system.
* False is also returned if this feature isn't supported for the current permissions system.
*/
boolean removeFromGroup(Player player, String group);
@ -69,7 +72,7 @@ public interface PermissionHandler {
* @param group The name of the group.
*
* @return True if succeed, false otherwise.
* False is also returned if this feature isn't supported for the current permissions system.
* False is also returned if this feature isn't supported for the current permissions system.
*/
boolean setGroup(Player player, String group);
@ -80,7 +83,7 @@ public interface PermissionHandler {
*
* @return Permission groups, or an empty list if this feature is not supported.
*/
List<String> getGroups(Player player);
Collection<String> getGroups(Player player);
/**
* Get the primary group of a player, if available.
@ -89,7 +92,13 @@ public interface PermissionHandler {
*
* @return The name of the primary permission group. Or null.
*/
String getPrimaryGroup(Player player);
default String getPrimaryGroup(Player player) {
Collection<String> groups = getGroups(player);
if (Utils.isCollectionEmpty(groups)) {
return null;
}
return groups.iterator().next();
}
/**
* Get the permission system that is being used.

View File

@ -12,6 +12,11 @@ import org.bukkit.plugin.PluginManager;
import java.util.ArrayList;
import java.util.List;
/**
* Handler for PermissionsBukkit.
*
* @see <a href="https://dev.bukkit.org/projects/permbukkit">PermissionsBukkit Bukkit page</a>
*/
public class PermissionsBukkitHandler implements PermissionHandler {
private PermissionsPlugin permissionsBukkitInstance;
@ -26,7 +31,8 @@ public class PermissionsBukkitHandler implements PermissionHandler {
@Override
public boolean addToGroup(Player player, String group) {
return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player addgroup " + player.getName() + " " + group);
return Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"permissions player addgroup " + player.getName() + " " + group);
}
@Override
@ -39,46 +45,27 @@ public class PermissionsBukkitHandler implements PermissionHandler {
return false;
}
@Override
public boolean isInGroup(Player player, String group) {
List<String> groupNames = getGroups(player);
return groupNames.contains(group);
}
@Override
public boolean removeFromGroup(Player player, String group) {
return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player removegroup " + player.getName() + " " + group);
return Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"permissions player removegroup " + player.getName() + " " + group);
}
@Override
public boolean setGroup(Player player, String group) {
return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player setgroup " + player.getName() + " " + group);
return Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"permissions player setgroup " + player.getName() + " " + group);
}
@Override
public List<String> getGroups(Player player) {
List<String> groups = new ArrayList<String>();
List<String> groups = new ArrayList<>();
for (Group group : permissionsBukkitInstance.getGroups(player.getUniqueId())) {
groups.add(group.getName());
}
return groups;
}
@Override
public String getPrimaryGroup(Player player) {
// Get the groups of the player
List<String> groups = getGroups(player);
// Make sure there is any group available, or return null
if (groups.isEmpty()) {
return null;
}
// Return the first group
return groups.get(0);
}
@Override
public PermissionsSystemType getPermissionSystem() {
return PermissionsSystemType.PERMISSIONS_BUKKIT;

View File

@ -10,6 +10,12 @@ import ru.tehkode.permissions.bukkit.PermissionsEx;
import java.util.ArrayList;
import java.util.List;
/**
* Handler for PermissionsEx.
*
* @see <a href="https://dev.bukkit.org/projects/permissionsex">PermissionsEx Bukkit page</a>
* @see <a href="https://github.com/PEXPlugins/PermissionsEx">PermissionsEx on Github</a>
*/
public class PermissionsExHandler implements PermissionHandler {
private PermissionManager permissionManager;
@ -72,17 +78,6 @@ public class PermissionsExHandler implements PermissionHandler {
return user.getParentIdentifiers(null);
}
@Override
public String getPrimaryGroup(Player player) {
PermissionUser user = permissionManager.getUser(player);
List<String> groups = user.getParentIdentifiers(null);
if (groups.isEmpty())
return null;
return groups.get(0);
}
@Override
public PermissionsSystemType getPermissionSystem() {
return PermissionsSystemType.PERMISSIONS_EX;

Some files were not shown because too many files have changed in this diff Show More