mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2025-02-02 13:01:28 +01:00
commit
1f02ef6f6e
185
.checkstyle.xml
Normal file
185
.checkstyle.xml
Normal 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
30
.codeclimate.yml
Normal 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
4
.gitignore
vendored
@ -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
|
||||
|
@ -1,4 +1,8 @@
|
||||
sudo: false
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- oracle-java8-installer
|
||||
|
||||
language: java
|
||||
jdk: oraclejdk8
|
||||
|
44
README.md
44
README.md
@ -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
|
||||
|
@ -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** <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** <oldEmail> <newEmail>: Change an email address of your account.
|
||||
<br />Requires `authme.player.email.change`
|
||||
- **/email recover** <email> [code]: Recover your account using an Email address by sending a mail containing a new password.
|
||||
- **/email recover** <email>: Recover your account using an Email address by sending a mail containing a new password.
|
||||
<br />Requires `authme.player.email.recover`
|
||||
- **/email code** <code>: Recover your account by submitting a code delivered to your email.
|
||||
<br />Requires `authme.player.email.recover`
|
||||
- **/email setpassword** <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** <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
|
||||
|
149
docs/config.md
149
docs/config.md
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 |
|
||||
---- | -------- | ---------: | ------
|
||||
[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
58
pom.xml
@ -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>
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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");
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
}
|
||||
});
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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(" -- ");
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
// Recovery code is needed; generate and send one
|
||||
recoveryService.createAndSendRecoveryCode(player, email);
|
||||
} else {
|
||||
// Process /email recovery addr@example.com 12394
|
||||
processRecoveryCode(player, arguments.get(1), 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;
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>";
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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/<uuid or name>/" 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
170
src/main/java/fr/xephi/authme/data/limbo/LimboService.java
Normal file
170
src/main/java/fr/xephi/authme/data/limbo/LimboService.java
Normal 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);
|
||||
}
|
||||
}
|
106
src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java
Normal file
106
src/main/java/fr/xephi/authme/data/limbo/LimboServiceHelper.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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/<uuid or name>/" 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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());
|
||||
@ -479,17 +516,51 @@ public class MySQL implements DataSource {
|
||||
rs = pst.executeQuery();
|
||||
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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,34 +48,28 @@ 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.Graph languageGraph = metrics.createGraph("Messages Language");
|
||||
final String messagesLanguage = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
|
||||
languageGraph.addPlotter(new Metrics.Plotter(messagesLanguage) {
|
||||
metrics.addCustomChart(new Metrics.SimplePie("messages_language") {
|
||||
@Override
|
||||
public int getValue() {
|
||||
return 1;
|
||||
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) {
|
||||
metrics.addCustomChart(new Metrics.SimplePie("database_backend") {
|
||||
@Override
|
||||
public int getValue() {
|
||||
return 1;
|
||||
public String getValue() {
|
||||
return settings.getProperty(DatabaseSettings.BACKEND).toString();
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
125
src/main/java/fr/xephi/authme/mail/EmailService.java
Normal file
125
src/main/java/fr/xephi/authme/mail/EmailService.java
Normal 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));
|
||||
}
|
||||
}
|
@ -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:
|
||||
if (settings.getProperty(EmailSettings.PORT25_USE_TLS)) {
|
||||
email.setStartTLSEnabled(true);
|
||||
email.setSSLCheckServerIdentity(true);
|
||||
}
|
||||
break;
|
||||
case 465:
|
||||
email.setSslSmtpPort(Integer.toString(port));
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 <password> */
|
||||
/** Usage: /login <password> */
|
||||
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 <password>" */
|
||||
/** Please, login with the command: /login <password> */
|
||||
LOGIN_MESSAGE("login_msg"),
|
||||
|
||||
/** Please, register to the server with the command "/register <password> <ConfirmPassword>" */
|
||||
/** Please, register to the server with the command: /register <password> <ConfirmPassword> */
|
||||
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 <password> <ConfirmPassword> */
|
||||
/** Usage: /register <password> <ConfirmPassword> */
|
||||
USAGE_REGISTER("usage_reg"),
|
||||
|
||||
/** Usage: /unregister <password> */
|
||||
/** Usage: /unregister <password> */
|
||||
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 <oldPassword> <newPassword> */
|
||||
/** Usage: /changepassword <oldPassword> <newPassword> */
|
||||
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 <yourEmail> <confirmEmail>" */
|
||||
/** Please add your email to your account with the command: /email add <yourEmail> <confirmEmail> */
|
||||
ADD_EMAIL_MESSAGE("add_email"),
|
||||
|
||||
/** Forgot your password? Please use the command "/email recovery <yourEmail>" */
|
||||
/** Forgot your password? Please use the command: /email recovery <yourEmail> */
|
||||
FORGOT_PASSWORD_MESSAGE("recovery_email"),
|
||||
|
||||
/** To login you have to solve a captcha code, please use the command "/captcha <theCaptcha>" */
|
||||
/** To login you have to solve a captcha code, please use the command: /captcha <theCaptcha> */
|
||||
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 <email> <confirmEmail> */
|
||||
/** Usage: /email add <email> <confirmEmail> */
|
||||
USAGE_ADD_EMAIL("usage_email_add"),
|
||||
|
||||
/** Usage: /email change <oldEmail> <newEmail> */
|
||||
/** Usage: /email change <oldEmail> <newEmail> */
|
||||
USAGE_CHANGE_EMAIL("usage_email_change"),
|
||||
|
||||
/** Usage: /email recovery <Email> */
|
||||
/** Usage: /email recovery <Email> */
|
||||
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;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()]));
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
@ -48,7 +49,9 @@ public interface PermissionHandler {
|
||||
* @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.
|
||||
*/
|
||||
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.
|
||||
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user