Merge branch 'master' of https://github.com/AuthMe/AuthMeReloaded into 1128-camel-case-rename

This commit is contained in:
ljacqu 2017-03-17 18:50:57 +01:00
commit 8ebb3c6b5a
203 changed files with 5111 additions and 1980 deletions

164
.checkstyle.xml Normal file
View File

@ -0,0 +1,164 @@
<?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">
<property name="format" value="TODO|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="FallThrough"/>
<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="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="MethodParamPad"/>
<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"/>
<property name="tokens" value="METHOD_DEF, ANNOTATION_FIELD_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>
<module name="FileTabCharacter"/>
</module>

30
.codeclimate.yml Normal file
View File

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

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
### Java files ### ### Java files ###
*.class *.class
MANIFEST.MF
# Package Files # Package Files
#*.jar #*.jar

View File

@ -19,20 +19,14 @@
- Project status: - Project status:
- Dependencies: [![Dependencies status](https://www.versioneye.com/user/projects/57b182e8d6ffcd0032d7cf2d/badge.svg)](https://www.versioneye.com/user/projects/57b182e8d6ffcd0032d7cf2d) - 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) - 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: - Development resources:
- <a href="http://ci.xephi.fr/job/AuthMeReloaded/javadoc/">JavaDocs</a> - <a href="http://ci.xephi.fr/job/AuthMeReloaded/javadoc/">JavaDocs</a>
- <a href="http://ci.xephi.fr/plugin/repository/everything/">Maven Repository</a> - <a href="http://ci.xephi.fr/plugin/repository/everything/">Maven Repository</a>
#####Statistics: - Statistics:
- bStats: [AuthMe on bstats.org](https://bstats.org/plugin/bukkit/AuthMe)
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">
<hr> <hr>
@ -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>DoubleSaltedMD5: SALTED2MD5</li>
<li>WordPress: WORDPRESS</li> <li>WordPress: WORDPRESS</li>
</ul></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>Cached database queries!</strong></li>
<li><strong>Fully compatible with Citizens2, CombatTag, CombatTagPlus!</strong></li> <li><strong>Fully compatible with Citizens2, CombatTag, CombatTagPlus!</strong></li>
<li>Compatible with Minecraft mods like <strong>BuildCraft or RedstoneCraft</strong></li> <li>Compatible with Minecraft mods like <strong>BuildCraft or RedstoneCraft</strong></li>

View File

@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly --> <!-- 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 Sat Feb 25 21:59:18 CET 2017. See docs/config/config.tpl.md -->
## AuthMe Configuration ## AuthMe Configuration
The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder, The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder,
@ -18,9 +18,11 @@ DataSource:
mySQLHost: '127.0.0.1' mySQLHost: '127.0.0.1'
# Database port # Database port
mySQLPort: '3306' mySQLPort: '3306'
# Username about Database Connection Infos # Connect to MySQL database over SSL
mySQLUseSSL: true
# Username to connect to the MySQL database
mySQLUsername: 'authme' mySQLUsername: 'authme'
# Password about Database Connection Infos # Password to connect to the MySQL database
mySQLPassword: '12345' mySQLPassword: '12345'
# Database Name, use with converters or as SQLITE database name # Database Name, use with converters or as SQLITE database name
mySQLDatabase: 'authme' mySQLDatabase: 'authme'
@ -34,8 +36,6 @@ DataSource:
mySQLRealName: 'realname' mySQLRealName: 'realname'
# Column for storing players passwords # Column for storing players passwords
mySQLColumnPassword: 'password' mySQLColumnPassword: 'password'
# Request mysql over SSL
mySQLUseSSL: true
# Column for storing players emails # Column for storing players emails
mySQLColumnEmail: 'email' mySQLColumnEmail: 'email'
# Column for storing if a player is logged in or not # Column for storing if a player is logged in or not
@ -94,13 +94,8 @@ settings:
# expired, he will not need to authenticate. # expired, he will not need to authenticate.
enabled: false enabled: false
# After how many minutes should a session expire? # After how many minutes should a session expire?
# Remember that sessions will end only after the timeout, and # A player's session ends after the timeout or if his IP has changed
# 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
timeout: 10 timeout: 10
# Should the session expire if the player tries to log in with
# another IP address?
sessionExpireOnIpChange: true
# Message language, available languages: # Message language, available languages:
# https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md # https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md
messagesLanguage: 'en' messagesLanguage: 'en'
@ -161,6 +156,8 @@ settings:
# AllowedRestrictedUser: # AllowedRestrictedUser:
# - playername;127.0.0.1 # - playername;127.0.0.1
AllowedRestrictedUser: [] AllowedRestrictedUser: []
# Ban unknown IPs trying to log in with a restricted username?
banUnsafedIP: false
# Should unregistered players be kicked immediately? # Should unregistered players be kicked immediately?
kickNonRegistered: false kickNonRegistered: false
# Should players be kicked on wrong password? # Should players be kicked on wrong password?
@ -177,7 +174,7 @@ settings:
# After how many seconds should players who fail to login or register # After how many seconds should players who fail to login or register
# be kicked? Set to 0 to disable. # be kicked? Set to 0 to disable.
timeout: 30 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_]*' allowedNicknameCharacters: '[a-zA-Z0-9_]*'
# How far can unregistered players walk? # How far can unregistered players walk?
# Set to 0 for unlimited radius # Set to 0 for unlimited radius
@ -189,8 +186,6 @@ settings:
# Should we display all other accounts from a player when he joins? # Should we display all other accounts from a player when he joins?
# permission: /authme.admin.accounts # permission: /authme.admin.accounts
displayOtherAccounts: true displayOtherAccounts: true
# Ban ip when the ip is not the ip registered in database
banUnsafedIP: false
# Spawn priority; values: authme, essentials, multiverse, default # Spawn priority; values: authme, essentials, multiverse, default
spawnPriority: 'authme,essentials,multiverse,default' spawnPriority: 'authme,essentials,multiverse,default'
# Maximum Login authorized by IP # Maximum Login authorized by IP
@ -223,18 +218,6 @@ settings:
minPasswordLength: 5 minPasswordLength: 5
# Maximum length of password # Maximum length of password
passwordMaxLength: 30 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, # Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL,
# MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB, # MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB,
# PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (for developers only). See full list at # PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (for developers only). See full list at
@ -317,12 +300,24 @@ settings:
# Do we need to prevent people to login with another case? # Do we need to prevent people to login with another case?
# If Xephi is registered, then Xephi can login, but not XEPHI/xephi/XePhI # If Xephi is registered, then Xephi can login, but not XEPHI/xephi/XePhI
preventOtherCase: true preventOtherCase: true
permission: GroupOptions:
# Take care with this option; if you want # Enables switching a player to defined permission groups before they log in.
# to use group switching of AuthMe # See below for a detailed explanation.
# for unloggedIn players, set this setting to true. enablePermissionCheck: false
# Default is false. # This is a very important option: if a registered player joins the server
EnablePermissionCheck: false # 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:
# Email SMTP server host # Email SMTP server host
mailSMTP: 'smtp.gmail.com' mailSMTP: 'smtp.gmail.com'
@ -366,18 +361,13 @@ Hooks:
disableSocialSpy: false disableSocialSpy: false
# Do we need to force /motd Essentials command on join? # Do we need to force /motd Essentials command on join?
useEssentialsMotd: false useEssentialsMotd: false
GroupOptions:
# Unregistered permission group
UnregisteredPlayerGroup: ''
# Registered permission group
RegisteredPlayerGroup: ''
Protection: Protection:
# Enable some servers protection (country based login, antibot) # Enable some servers protection (country based login, antibot)
enableProtection: false enableProtection: false
# Apply the protection also to registered usernames # Apply the protection also to registered usernames
enableProtectionRegistered: true enableProtectionRegistered: true
# Countries allowed to join the server and register. For country codes, see # 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! # PLEASE USE QUOTES!
countries: countries:
- 'US' - 'US'
@ -432,6 +422,8 @@ Security:
maxLoginTry: 5 maxLoginTry: 5
# Captcha length # Captcha length
captchaLength: 5 captchaLength: 5
# Minutes after which login attempts count is reset for a player
captchaCountReset: 60
tempban: tempban:
# Tempban a user's IP address if they enter the wrong password too many times # Tempban a user's IP address if they enter the wrong password too many times
enableTempban: false enableTempban: false
@ -448,6 +440,10 @@ Security:
length: 8 length: 8
# How many hours is a recovery code valid for? # How many hours is a recovery code valid for?
validForHours: 4 validForHours: 4
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
BackupSystem: BackupSystem:
# Enable or disable automatic backup # Enable or disable automatic backup
ActivateBackup: false ActivateBackup: false
@ -464,4 +460,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 Sat Feb 25 21:59:18 CET 2017

View File

@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly --> <!-- 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 Mon Mar 13 20:34:31 CET 2017. See docs/translations/translations.tpl.md -->
# AuthMe Translations # AuthMe Translations
The following translations are available in AuthMe. Set `messagesLanguage` to the language code The following translations are available in AuthMe. Set `messagesLanguage` to the language code
@ -8,35 +8,34 @@ in your config.yml to use the language, or use another language code to start a
Code | Language | Translated | &nbsp; Code | Language | Translated | &nbsp;
---- | -------- | ---------: | ------ ---- | -------- | ---------: | ------
[en](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_en.yml) | English | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" /> [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" /> [bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&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" /> [br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 89% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=89&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" /> [cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 89% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=89&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" /> [de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 89% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=89&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" /> [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" /> [eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 55% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb6600&w=55&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" /> [fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 59% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=59&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" /> [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" /> [gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 63% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=63&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" /> [hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 88% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=88&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" /> [id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 63% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=63&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" /> [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" /> [ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 64% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=64&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" /> [lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 47% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa5500&w=47&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" /> [nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 89% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=89&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" /> [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" /> [pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&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" /> [ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 88% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=88&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" /> [ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&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" /> [sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 41% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa4400&w=41&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" /> [tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&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" /> [uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 83% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=83&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" /> [vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 89% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=89&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" /> [zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 89% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=89&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" /> [zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 72% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=72&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" /> [zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 86% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=86&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" /> [zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 72% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=72&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 Mon Mar 13 20:34:31 CET 2017

58
pom.xml
View File

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

View File

@ -14,6 +14,7 @@ import fr.xephi.authme.initialization.OnShutdownPlayerSaver;
import fr.xephi.authme.initialization.OnStartupTasks; import fr.xephi.authme.initialization.OnStartupTasks;
import fr.xephi.authme.initialization.SettingsProvider; import fr.xephi.authme.initialization.SettingsProvider;
import fr.xephi.authme.initialization.TaskCloser; import fr.xephi.authme.initialization.TaskCloser;
import fr.xephi.authme.initialization.factory.FactoryDependencyHandler;
import fr.xephi.authme.listener.BlockListener; import fr.xephi.authme.listener.BlockListener;
import fr.xephi.authme.listener.EntityListener; import fr.xephi.authme.listener.EntityListener;
import fr.xephi.authme.listener.PlayerListener; import fr.xephi.authme.listener.PlayerListener;
@ -27,19 +28,18 @@ import fr.xephi.authme.permission.PermissionsSystemType;
import fr.xephi.authme.security.crypts.Sha256; import fr.xephi.authme.security.crypts.Sha256;
import fr.xephi.authme.service.BackupService; import fr.xephi.authme.service.BackupService;
import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.GeoIpService;
import fr.xephi.authme.service.MigrationService; import fr.xephi.authme.service.MigrationService;
import fr.xephi.authme.settings.Settings; 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.PluginSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.task.CleanupTask; import fr.xephi.authme.task.CleanupTask;
import fr.xephi.authme.task.purge.PurgeService; 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.Server;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@ -72,8 +72,6 @@ public class AuthMe extends JavaPlugin {
private DataSource database; private DataSource database;
private BukkitService bukkitService; private BukkitService bukkitService;
private Injector injector; private Injector injector;
private GeoIpService geoIpService;
private PlayerCache playerCache;
/** /**
* Constructor. * Constructor.
@ -139,6 +137,7 @@ public class AuthMe extends JavaPlugin {
initialize(); initialize();
} catch (Exception e) { } catch (Exception e) {
ConsoleLogger.logException("Aborting initialization of AuthMe:", e); ConsoleLogger.logException("Aborting initialization of AuthMe:", e);
OnStartupTasks.displayLegacyJarHint(e);
stopOrUnload(); stopOrUnload();
return; return;
} }
@ -197,11 +196,19 @@ public class AuthMe extends JavaPlugin {
ConsoleLogger.setLogger(getLogger()); ConsoleLogger.setLogger(getLogger());
ConsoleLogger.setLogFile(new File(getDataFolder(), LOG_FILENAME)); 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 // Create plugin folder
getDataFolder().mkdir(); getDataFolder().mkdir();
// Create injector, provide elements from the Bukkit environment and register providers // 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())
.addDefaultHandlers("fr.xephi.authme")
.create();
injector.register(AuthMe.class, this); injector.register(AuthMe.class, this);
injector.register(Server.class, getServer()); injector.register(Server.class, getServer());
injector.register(PluginManager.class, getServer().getPluginManager()); injector.register(PluginManager.class, getServer().getPluginManager());
@ -242,14 +249,13 @@ public class AuthMe extends JavaPlugin {
*/ */
protected void instantiateServices(Injector injector) { protected void instantiateServices(Injector injector) {
// PlayerCache is still injected statically sometimes // PlayerCache is still injected statically sometimes
playerCache = PlayerCache.getInstance(); PlayerCache playerCache = PlayerCache.getInstance();
injector.register(PlayerCache.class, playerCache); injector.register(PlayerCache.class, playerCache);
database = injector.getSingleton(DataSource.class); database = injector.getSingleton(DataSource.class);
permsMan = injector.getSingleton(PermissionsManager.class); permsMan = injector.getSingleton(PermissionsManager.class);
bukkitService = injector.getSingleton(BukkitService.class); bukkitService = injector.getSingleton(BukkitService.class);
commandHandler = injector.getSingleton(CommandHandler.class); commandHandler = injector.getSingleton(CommandHandler.class);
geoIpService = injector.getSingleton(GeoIpService.class);
// Trigger construction of API classes; they will keep track of the singleton // Trigger construction of API classes; they will keep track of the singleton
injector.getSingleton(NewAPI.class); injector.getSingleton(NewAPI.class);
@ -270,6 +276,12 @@ public class AuthMe extends JavaPlugin {
&& settings.getProperty(PluginSettings.SESSIONS_ENABLED)) { && settings.getProperty(PluginSettings.SESSIONS_ENABLED)) {
ConsoleLogger.warning("WARNING!!! You set session timeout to 0, this may cause security issues!"); 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");
}
} }
/** /**
@ -344,24 +356,6 @@ public class AuthMe extends JavaPlugin {
ConsoleLogger.close(); 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. * Handle Bukkit commands.
* *

View File

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

View File

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

View File

@ -24,6 +24,7 @@ import java.util.List;
* NewAPI authmeApi = AuthMe.getApi(); * NewAPI authmeApi = AuthMe.getApi();
* </code> * </code>
*/ */
@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) // Justification: Class name cannot be changed anymore
public class NewAPI { public class NewAPI {
private static NewAPI singleton; private static NewAPI singleton;

View File

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

View File

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

View File

@ -24,6 +24,7 @@ import fr.xephi.authme.command.executable.authme.SpawnCommand;
import fr.xephi.authme.command.executable.authme.SwitchAntiBotCommand; import fr.xephi.authme.command.executable.authme.SwitchAntiBotCommand;
import fr.xephi.authme.command.executable.authme.UnregisterAdminCommand; import fr.xephi.authme.command.executable.authme.UnregisterAdminCommand;
import fr.xephi.authme.command.executable.authme.VersionCommand; 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.captcha.CaptchaCommand;
import fr.xephi.authme.command.executable.changepassword.ChangePasswordCommand; import fr.xephi.authme.command.executable.changepassword.ChangePasswordCommand;
import fr.xephi.authme.command.executable.email.AddEmailCommand; import fr.xephi.authme.command.executable.email.AddEmailCommand;
@ -37,6 +38,7 @@ import fr.xephi.authme.command.executable.register.RegisterCommand;
import fr.xephi.authme.command.executable.unregister.UnregisterCommand; import fr.xephi.authme.command.executable.unregister.UnregisterCommand;
import fr.xephi.authme.permission.AdminPermission; import fr.xephi.authme.permission.AdminPermission;
import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.permission.PlayerPermission;
import fr.xephi.authme.permission.PlayerStatePermission;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -298,6 +300,19 @@ public class CommandInitializer {
.executableCommand(MessagesCommand.class) .executableCommand(MessagesCommand.class)
.register(); .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 // Register the base login command
final CommandDescription LOGIN_BASE = CommandDescription.builder() final CommandDescription LOGIN_BASE = CommandDescription.builder()
.parent(null) .parent(null)

View File

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

View File

@ -1,6 +1,5 @@
package fr.xephi.authme.command.executable.authme; package fr.xephi.authme.command.executable.authme;
import ch.jalu.injector.Injector;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
@ -13,6 +12,7 @@ import fr.xephi.authme.datasource.converter.RoyalAuthConverter;
import fr.xephi.authme.datasource.converter.SqliteToSql; import fr.xephi.authme.datasource.converter.SqliteToSql;
import fr.xephi.authme.datasource.converter.vAuthConverter; import fr.xephi.authme.datasource.converter.vAuthConverter;
import fr.xephi.authme.datasource.converter.xAuthConverter; import fr.xephi.authme.datasource.converter.xAuthConverter;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.CommonService;
@ -37,7 +37,7 @@ public class ConverterCommand implements ExecutableCommand {
private BukkitService bukkitService; private BukkitService bukkitService;
@Inject @Inject
private Injector injector; private Factory<Converter> converterFactory;
@Override @Override
public void executeCommand(final CommandSender sender, List<String> arguments) { public void executeCommand(final CommandSender sender, List<String> arguments) {
@ -52,7 +52,7 @@ public class ConverterCommand implements ExecutableCommand {
} }
// Get the proper converter instance // Get the proper converter instance
final Converter converter = injector.newInstance(converterClass); final Converter converter = converterFactory.newInstance(converterClass);
// Run the convert job // Run the convert job
bukkitService.runTaskAsynchronously(new Runnable() { bukkitService.runTaskAsynchronously(new Runnable() {

View File

@ -11,10 +11,10 @@ import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings; import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.util.Utils;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Collection;
import java.util.List; import java.util.List;
/** /**
@ -44,8 +44,7 @@ public class ReloadCommand implements ExecutableCommand {
ConsoleLogger.setLoggingOptions(settings); ConsoleLogger.setLoggingOptions(settings);
// We do not change database type for consistency issues, but we'll output a note in the logs // 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())) { if (!settings.getProperty(DatabaseSettings.BACKEND).equals(dataSource.getType())) {
ConsoleLogger.info("Note: cannot change database type during /authme reload"); Utils.logAndSendMessage(sender, "Note: cannot change database type during /authme reload");
sender.sendMessage("Note: cannot change database type during /authme reload");
} }
performReloadOnServices(); performReloadOnServices();
commonService.send(sender, MessageKey.CONFIG_RELOAD_SUCCESS); commonService.send(sender, MessageKey.CONFIG_RELOAD_SUCCESS);
@ -57,14 +56,10 @@ public class ReloadCommand implements ExecutableCommand {
} }
private void performReloadOnServices() { private void performReloadOnServices() {
Collection<Reloadable> reloadables = injector.retrieveAllOfType(Reloadable.class); injector.retrieveAllOfType(Reloadable.class)
for (Reloadable reloadable : reloadables) { .forEach(r -> r.reload());
reloadable.reload();
}
Collection<SettingsDependent> settingsDependents = injector.retrieveAllOfType(SettingsDependent.class); injector.retrieveAllOfType(SettingsDependent.class)
for (SettingsDependent dependent : settingsDependents) { .forEach(s -> s.reload(settings));
dependent.reload(settings);
}
} }
} }

View File

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

View File

@ -0,0 +1,58 @@
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Debug command main.
*/
public class DebugCommand implements ExecutableCommand {
@Inject
private Factory<DebugSection> debugSectionFactory;
private Set<Class<? extends DebugSection>> sectionClasses =
ImmutableSet.of(PermissionGroups.class, TestEmailSender.class, CountryLookup.class);
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 HashMap<>();
for (Class<? extends DebugSection> sectionClass : sectionClasses) {
DebugSection section = debugSectionFactory.newInstance(sectionClass);
sections.put(section.getName(), section);
}
this.sections = sections;
}
return sections;
}
}

View File

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

View File

@ -0,0 +1,40 @@
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)));
}
}
}

View File

@ -0,0 +1,100 @@
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 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 (email.contains("@")) {
return email;
}
sender.sendMessage(ChatColor.RED + "Invalid email! Usage: /authme debug mail test@example.com");
return null;
}
}
private boolean sendTestEmail(String email) {
HtmlEmail htmlEmail;
try {
htmlEmail = sendMailSSL.initializeMail(email);
} catch (EmailException e) {
ConsoleLogger.logException("Failed to create email for sample email:", e);
return false;
}
htmlEmail.setSubject("AuthMe test email");
String message = "Hello there!<br />This is a sample email sent to you from a Minecraft server ("
+ server.getName() + ") via /authme debug mail. If you're seeing this, sending emails should be fine.";
return sendMailSSL.sendEmail(message, htmlEmail);
}
}

View File

@ -3,7 +3,6 @@ package fr.xephi.authme.command.executable.captcha;
import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.CaptchaManager; import fr.xephi.authme.data.CaptchaManager;
import fr.xephi.authme.data.auth.PlayerCache; 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.LimboCache;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.CommonService;

View File

@ -5,24 +5,31 @@ import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.mail.SendMailSSL; import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.CommonService; import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.RecoveryCodeService; import fr.xephi.authme.service.RecoveryCodeService;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.RandomStringUtils; import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.util.expiring.Duration;
import fr.xephi.authme.util.expiring.ExpiringSet;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import javax.annotation.PostConstruct;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH; import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
/** /**
* Command for password recovery by email. * Command for password recovery by email.
*/ */
public class RecoverEmailCommand extends PlayerCommand { public class RecoverEmailCommand extends PlayerCommand implements Reloadable {
@Inject @Inject
private PasswordSecurity passwordSecurity; private PasswordSecurity passwordSecurity;
@ -37,17 +44,28 @@ public class RecoverEmailCommand extends PlayerCommand {
private PlayerCache playerCache; private PlayerCache playerCache;
@Inject @Inject
private SendMailSSL sendMailSsl; private EmailService emailService;
@Inject @Inject
private RecoveryCodeService recoveryCodeService; private RecoveryCodeService recoveryCodeService;
@Inject
private Messages messages;
private ExpiringSet<String> emailCooldown;
@PostConstruct
private void initEmailCooldownSet() {
emailCooldown = new ExpiringSet<>(
commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS);
}
@Override @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 playerMail = arguments.get(0);
final String playerName = player.getName(); final String playerName = player.getName();
if (!sendMailSsl.hasAllInformation()) { if (!emailService.hasAllInformation()) {
ConsoleLogger.warning("Mail API is not set"); ConsoleLogger.warning("Mail API is not set");
commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS); commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS);
return; return;
@ -78,15 +96,29 @@ public class RecoverEmailCommand extends PlayerCommand {
processRecoveryCode(player, arguments.get(1), email); processRecoveryCode(player, arguments.get(1), email);
} }
} else { } else {
boolean maySendMail = checkEmailCooldown(player);
if (maySendMail) {
generateAndSendNewPassword(player, email); generateAndSendNewPassword(player, email);
} }
} }
}
@Override
public void reload() {
emailCooldown.setExpiration(
commonService.getProperty(SecuritySettings.EMAIL_RECOVERY_COOLDOWN_SECONDS), TimeUnit.SECONDS);
}
private void createAndSendRecoveryCode(Player player, String email) { private void createAndSendRecoveryCode(Player player, String email) {
if (!checkEmailCooldown(player)) {
return;
}
String recoveryCode = recoveryCodeService.generateCode(player.getName()); String recoveryCode = recoveryCodeService.generateCode(player.getName());
boolean couldSendMail = sendMailSsl.sendRecoveryCode(player.getName(), email, recoveryCode); boolean couldSendMail = emailService.sendRecoveryCode(player.getName(), email, recoveryCode);
if (couldSendMail) { if (couldSendMail) {
commonService.send(player, MessageKey.RECOVERY_CODE_SENT); commonService.send(player, MessageKey.RECOVERY_CODE_SENT);
emailCooldown.add(player.getName().toLowerCase());
} else { } else {
commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
} }
@ -108,11 +140,22 @@ public class RecoverEmailCommand extends PlayerCommand {
HashedPassword hashNew = passwordSecurity.computeHash(thePass, name); HashedPassword hashNew = passwordSecurity.computeHash(thePass, name);
dataSource.updatePassword(name, hashNew); dataSource.updatePassword(name, hashNew);
boolean couldSendMail = sendMailSsl.sendPasswordMail(name, email, thePass); boolean couldSendMail = emailService.sendPasswordMail(name, email, thePass);
if (couldSendMail) { if (couldSendMail) {
commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
emailCooldown.add(player.getName().toLowerCase());
} else { } else {
commonService.send(player, MessageKey.EMAIL_SEND_FAILURE); commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
} }
} }
private boolean checkEmailCooldown(Player player) {
Duration waitDuration = emailCooldown.getExpiration(player.getName().toLowerCase());
if (waitDuration.getDuration() > 0) {
String durationText = messages.formatDuration(waitDuration);
messages.send(player, MessageKey.EMAIL_COOLDOWN_ERROR, durationText);
return false;
}
return true;
}
} }

View File

@ -2,7 +2,7 @@ package fr.xephi.authme.command.executable.register;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.PlayerCommand; 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.message.MessageKey;
import fr.xephi.authme.process.Management; import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.RegisterSecondaryArgument; import fr.xephi.authme.process.register.RegisterSecondaryArgument;
@ -37,7 +37,7 @@ public class RegisterCommand extends PlayerCommand {
private CommonService commonService; private CommonService commonService;
@Inject @Inject
private SendMailSSL sendMailSsl; private EmailService emailService;
@Inject @Inject
private ValidationService validationService; private ValidationService validationService;
@ -127,7 +127,7 @@ public class RegisterCommand extends PlayerCommand {
} }
private void handleEmailRegistration(Player player, List<String> arguments) { private void handleEmailRegistration(Player player, List<String> arguments) {
if (!sendMailSsl.hasAllInformation()) { if (!emailService.hasAllInformation()) {
commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS); commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS);
ConsoleLogger.warning("Cannot register player '" + player.getName() + "': no email or password is set " 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()); + "to send emails from. Please adjust your config at " + EmailSettings.MAIL_ACCOUNT.getPath());

View File

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

View File

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

View File

@ -1,21 +1,21 @@
package fr.xephi.authme.data; package fr.xephi.authme.data;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.initialization.HasCleanup; import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.SettingsDependent; import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages; import fr.xephi.authme.message.Messages;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.PlayerUtils;
import fr.xephi.authme.util.expiring.TimedCounter;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Date; import java.util.Date;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; 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.settings.properties.SecuritySettings.TEMPBAN_MINUTES_BEFORE_RESET;
import static fr.xephi.authme.util.Utils.MILLIS_PER_MINUTE; 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 { 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 BukkitService bukkitService;
private final Messages messages; private final Messages messages;
@ -50,18 +50,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
*/ */
public void increaseCount(String address, String name) { public void increaseCount(String address, String name) {
if (isEnabled) { if (isEnabled) {
Map<String, TimedCounter> countsByName = ipLoginFailureCounts.get(address); TimedCounter<String> countsByName = ipLoginFailureCounts.computeIfAbsent(
if (countsByName == null) { address, k -> new TimedCounter<>(resetThreshold, TimeUnit.MINUTES));
countsByName = new ConcurrentHashMap<>(); countsByName.increment(name);
ipLoginFailureCounts.put(address, countsByName);
}
TimedCounter counter = countsByName.get(name);
if (counter == null) {
countsByName.put(name, new TimedCounter(1));
} else {
counter.increment(resetThreshold);
}
} }
} }
@ -73,9 +64,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
*/ */
public void resetCount(String address, String name) { public void resetCount(String address, String name) {
if (isEnabled) { if (isEnabled) {
Map<String, TimedCounter> map = ipLoginFailureCounts.get(address); TimedCounter<String> counter = ipLoginFailureCounts.get(address);
if (map != null) { if (counter != null) {
map.remove(name); counter.remove(name);
} }
} }
} }
@ -88,13 +79,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
*/ */
public boolean shouldTempban(String address) { public boolean shouldTempban(String address) {
if (isEnabled) { if (isEnabled) {
Map<String, TimedCounter> countsByName = ipLoginFailureCounts.get(address); TimedCounter<String> countsByName = ipLoginFailureCounts.get(address);
if (countsByName != null) { if (countsByName != null) {
int total = 0; return countsByName.total() >= threshold;
for (TimedCounter counter : countsByName.values()) {
total += counter.getCount(resetThreshold);
}
return total >= threshold;
} }
} }
return false; return false;
@ -137,56 +124,9 @@ public class TempbanManager implements SettingsDependent, HasCleanup {
@Override @Override
public void performCleanup() { public void performCleanup() {
for (Map<String, TimedCounter> countsByIp : ipLoginFailureCounts.values()) { for (TimedCounter<String> countsByIp : ipLoginFailureCounts.values()) {
Iterator<TimedCounter> it = countsByIp.values().iterator(); countsByIp.removeExpiredEntries();
while (it.hasNext()) { }
TimedCounter counter = it.next(); ipLoginFailureCounts.entrySet().removeIf(e -> e.getValue().isEmpty());
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();
}
} }
} }

View File

@ -1,11 +1,8 @@
package fr.xephi.authme.data.limbo; package fr.xephi.authme.data.limbo;
import fr.xephi.authme.data.backup.LimboPlayerStorage; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.SpawnLoader; 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.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -23,14 +20,11 @@ public class LimboCache {
private final Map<String, LimboPlayer> cache = new ConcurrentHashMap<>(); private final Map<String, LimboPlayer> cache = new ConcurrentHashMap<>();
private LimboPlayerStorage limboPlayerStorage; private LimboPlayerStorage limboPlayerStorage;
private Settings settings;
private PermissionsManager permissionsManager; private PermissionsManager permissionsManager;
private SpawnLoader spawnLoader; private SpawnLoader spawnLoader;
@Inject @Inject
LimboCache(Settings settings, PermissionsManager permissionsManager, LimboCache(PermissionsManager permissionsManager, SpawnLoader spawnLoader, LimboPlayerStorage limboPlayerStorage) {
SpawnLoader spawnLoader, LimboPlayerStorage limboPlayerStorage) {
this.settings = settings;
this.permissionsManager = permissionsManager; this.permissionsManager = permissionsManager;
this.spawnLoader = spawnLoader; this.spawnLoader = spawnLoader;
this.limboPlayerStorage = limboPlayerStorage; this.limboPlayerStorage = limboPlayerStorage;
@ -52,6 +46,7 @@ public class LimboCache {
if (permissionsManager.hasGroupSupport()) { if (permissionsManager.hasGroupSupport()) {
playerGroup = permissionsManager.getPrimaryGroup(player); playerGroup = permissionsManager.getPrimaryGroup(player);
} }
ConsoleLogger.debug("Player `{0}` has primary group `{1}`", player.getName(), playerGroup);
if (limboPlayerStorage.hasData(player)) { if (limboPlayerStorage.hasData(player)) {
LimboPlayer cache = limboPlayerStorage.readData(player); LimboPlayer cache = limboPlayerStorage.readData(player);
@ -85,14 +80,13 @@ public class LimboCache {
float flySpeed = data.getFlySpeed(); float flySpeed = data.getFlySpeed();
// Reset the speed value if it was 0 // Reset the speed value if it was 0
if (walkSpeed < 0.01f) { if (walkSpeed < 0.01f) {
walkSpeed = 0.2f; walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED;
} }
if (flySpeed < 0.01f) { if (flySpeed < 0.01f) {
flySpeed = 0.2f; flySpeed = LimboPlayer.DEFAULT_FLY_SPEED;
} }
player.setWalkSpeed(walkSpeed); player.setWalkSpeed(walkSpeed);
player.setFlySpeed(flySpeed); player.setFlySpeed(flySpeed);
restoreGroup(player, data.getGroup());
data.clearTasks(); data.clearTasks();
} }
} }
@ -154,11 +148,4 @@ public class LimboCache {
removeFromCache(player); removeFromCache(player);
addPlayerData(player); addPlayerData(player);
} }
private void restoreGroup(Player player, String group) {
if (!StringUtils.isEmpty(group) && permissionsManager.hasGroupSupport()
&& settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) {
permissionsManager.setGroup(player, group);
}
}
} }

View File

@ -10,6 +10,9 @@ import org.bukkit.scheduler.BukkitTask;
*/ */
public class LimboPlayer { public 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 canFly;
private final boolean operator; private final boolean operator;
private final String group; private final String group;

View File

@ -1,4 +1,4 @@
package fr.xephi.authme.data.backup; package fr.xephi.authme.data.limbo;
import com.google.common.io.Files; import com.google.common.io.Files;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -10,11 +10,10 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer; import com.google.gson.JsonSerializer;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.settings.SpawnLoader;
import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.SpawnLoader;
import fr.xephi.authme.util.FileUtils; import fr.xephi.authme.util.FileUtils;
import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.Location; import org.bukkit.Location;
@ -149,8 +148,8 @@ public class LimboPlayerStorage {
String group = ""; String group = "";
boolean operator = false; boolean operator = false;
boolean canFly = false; boolean canFly = false;
float walkSpeed = 0.2f; float walkSpeed = LimboPlayer.DEFAULT_WALK_SPEED;
float flySpeed = 0.2f; float flySpeed = LimboPlayer.DEFAULT_FLY_SPEED;
JsonElement e; JsonElement e;
if ((e = jsonObject.getAsJsonObject("location")) != null) { if ((e = jsonObject.getAsJsonObject("location")) != null) {

View File

@ -6,6 +6,8 @@ import fr.xephi.authme.settings.properties.DatabaseSettings;
/** /**
* Database column names. * 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 class Columns {
public final String NAME; public final String NAME;

View File

@ -45,7 +45,10 @@ public class MySQL implements DataSource {
private HikariDataSource ds; private HikariDataSource ds;
private String phpBbPrefix; private String phpBbPrefix;
private String IPBPrefix;
private int phpBbGroup; private int phpBbGroup;
private int IPBGroup;
private int XFGroup;
private String wordpressPrefix; private String wordpressPrefix;
public MySQL(Settings settings) throws ClassNotFoundException, SQLException { public MySQL(Settings settings) throws ClassNotFoundException, SQLException {
@ -96,10 +99,13 @@ public class MySQL implements DataSource {
this.hashAlgorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH); this.hashAlgorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH);
this.phpBbPrefix = settings.getProperty(HooksSettings.PHPBB_TABLE_PREFIX); this.phpBbPrefix = settings.getProperty(HooksSettings.PHPBB_TABLE_PREFIX);
this.phpBbGroup = settings.getProperty(HooksSettings.PHPBB_ACTIVATED_GROUP_ID); 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.wordpressPrefix = settings.getProperty(HooksSettings.WORDPRESS_TABLE_PREFIX);
this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE); this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE);
if (poolSize == -1) { 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);
} }
@ -334,8 +340,39 @@ public class MySQL implements DataSource {
pst.close(); pst.close();
} }
} }
if (hashAlgorithm == HashAlgorithm.IPB4){
if (hashAlgorithm == HashAlgorithm.PHPBB) { 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 + "=?;"; sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
pst = con.prepareStatement(sql); pst = con.prepareStatement(sql);
pst.setString(1, auth.getNickname()); pst.setString(1, auth.getNickname());
@ -479,6 +516,7 @@ public class MySQL implements DataSource {
rs = pst.executeQuery(); rs = pst.executeQuery();
if (rs.next()) { if (rs.next()) {
int id = rs.getInt(col.ID); 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 (?,?,?)"; sql = "INSERT INTO xf_user_authenticate (user_id, scheme_class, data) VALUES (?,?,?)";
pst2 = con.prepareStatement(sql); pst2 = con.prepareStatement(sql);
pst2.setInt(1, id); pst2.setInt(1, id);
@ -490,6 +528,39 @@ public class MySQL implements DataSource {
pst2.setBlob(3, blob); pst2.setBlob(3, blob);
pst2.executeUpdate(); pst2.executeUpdate();
pst2.close(); 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(); rs.close();
pst.close(); pst.close();

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ package fr.xephi.authme.initialization;
import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache; import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.backup.LimboPlayerStorage; import fr.xephi.authme.data.limbo.LimboPlayerStorage;
import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.service.PluginHookService; import fr.xephi.authme.service.PluginHookService;

View File

@ -1,11 +1,13 @@
package fr.xephi.authme.initialization; package fr.xephi.authme.initialization;
import ch.jalu.injector.exceptions.InjectorReflectionException;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages; import fr.xephi.authme.message.Messages;
import org.bstats.Metrics;
import fr.xephi.authme.output.ConsoleFilter; import fr.xephi.authme.output.ConsoleFilter;
import fr.xephi.authme.output.Log4JFilter; import fr.xephi.authme.output.Log4JFilter;
import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.BukkitService;
@ -18,10 +20,9 @@ import fr.xephi.authme.util.StringUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.mcstats.Metrics;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.IOException; import java.util.Optional;
import java.util.logging.Logger; import java.util.logging.Logger;
import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE; import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE;
@ -44,34 +45,28 @@ public class OnStartupTasks {
OnStartupTasks() { OnStartupTasks() {
} }
/**
* Sends bstats metrics.
*
* @param plugin the plugin instance
* @param settings the settings
*/
public static void sendMetrics(AuthMe plugin, Settings settings) { public static void sendMetrics(AuthMe plugin, Settings settings) {
try {
final Metrics metrics = new Metrics(plugin); final Metrics metrics = new Metrics(plugin);
final Metrics.Graph languageGraph = metrics.createGraph("Messages Language"); metrics.addCustomChart(new Metrics.SimplePie("messages_language") {
final String messagesLanguage = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
languageGraph.addPlotter(new Metrics.Plotter(messagesLanguage) {
@Override @Override
public int getValue() { public String getValue() {
return 1; return settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
} }
}); });
final Metrics.Graph databaseBackend = metrics.createGraph("Database Backend"); metrics.addCustomChart(new Metrics.SimplePie("database_backend") {
final String dataSource = settings.getProperty(DatabaseSettings.BACKEND).toString();
databaseBackend.addPlotter(new Metrics.Plotter(dataSource) {
@Override @Override
public int getValue() { public String getValue() {
return 1; 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 +119,23 @@ public class OnStartupTasks {
} }
}, 1, TICKS_PER_MINUTE * settings.getProperty(EmailSettings.DELAY_RECALL)); }, 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/");
}
}
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,27 +1,19 @@
package fr.xephi.authme.mail; package fr.xephi.authme.mail;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.ConsoleLogger; 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.Settings;
import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.util.FileUtils;
import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.StringUtils;
import org.apache.commons.mail.EmailConstants; import org.apache.commons.mail.EmailConstants;
import org.apache.commons.mail.EmailException; import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail; import org.apache.commons.mail.HtmlEmail;
import org.bukkit.Server;
import javax.activation.CommandMap; import javax.activation.CommandMap;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.activation.MailcapCommandMap; import javax.activation.MailcapCommandMap;
import javax.imageio.ImageIO;
import javax.inject.Inject; import javax.inject.Inject;
import javax.mail.Session; import javax.mail.Session;
import java.io.File;
import java.io.IOException;
import java.security.Security; import java.security.Security;
import java.util.Properties; import java.util.Properties;
@ -34,16 +26,8 @@ import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD;
*/ */
public class SendMailSSL { public class SendMailSSL {
private final File dataFolder;
private final String serverName;
private final Settings settings;
@Inject @Inject
SendMailSSL(@DataFolder File dataFolder, Server server, Settings settings) { private Settings settings;
this.dataFolder = dataFolder;
this.serverName = server.getServerName();
this.settings = settings;
}
/** /**
* Returns whether all necessary settings are set for sending mails. * Returns whether all necessary settings are set for sending mails.
@ -55,76 +39,7 @@ public class SendMailSSL {
&& !settings.getProperty(MAIL_PASSWORD).isEmpty(); && !settings.getProperty(MAIL_PASSWORD).isEmpty();
} }
/** public HtmlEmail initializeMail(String emailAddress) throws EmailException {
* 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 {
String senderMail = StringUtils.isEmpty(settings.getProperty(EmailSettings.MAIL_ADDRESS)) String senderMail = StringUtils.isEmpty(settings.getProperty(EmailSettings.MAIL_ADDRESS))
? settings.getProperty(EmailSettings.MAIL_ACCOUNT) ? settings.getProperty(EmailSettings.MAIL_ACCOUNT)
: settings.getProperty(EmailSettings.MAIL_ADDRESS); : settings.getProperty(EmailSettings.MAIL_ADDRESS);
@ -143,13 +58,15 @@ public class SendMailSSL {
email.setFrom(senderMail, senderName); email.setFrom(senderMail, senderName);
email.setSubject(settings.getProperty(EmailSettings.RECOVERY_MAIL_SUBJECT)); email.setSubject(settings.getProperty(EmailSettings.RECOVERY_MAIL_SUBJECT));
email.setAuthentication(settings.getProperty(EmailSettings.MAIL_ACCOUNT), mailPassword); email.setAuthentication(settings.getProperty(EmailSettings.MAIL_ACCOUNT), mailPassword);
if (settings.getProperty(PluginSettings.LOG_LEVEL).includes(LogLevel.DEBUG)) {
email.setDebug(true);
}
setPropertiesForPort(email, port); setPropertiesForPort(email, port);
return email; return email;
} }
@VisibleForTesting public boolean sendEmail(String content, HtmlEmail email) {
boolean sendEmail(String content, HtmlEmail email) {
Thread.currentThread().setContextClassLoader(SendMailSSL.class.getClassLoader()); Thread.currentThread().setContextClassLoader(SendMailSSL.class.getClassLoader());
// Issue #999: Prevent UnsupportedDataTypeException: no object DCH for MIME type multipart/alternative // 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 // cf. http://stackoverflow.com/questions/21856211/unsupporteddatatypeexception-no-object-dch-for-mime-type
@ -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 { private void setPropertiesForPort(HtmlEmail email, int port) throws EmailException {
switch (port) { switch (port) {
case 587: case 587:
@ -214,8 +116,10 @@ public class SendMailSSL {
} }
break; break;
case 25: case 25:
if (settings.getProperty(EmailSettings.PORT25_USE_TLS)) {
email.setStartTLSEnabled(true); email.setStartTLSEnabled(true);
email.setSSLCheckServerIdentity(true); email.setSSLCheckServerIdentity(true);
}
break; break;
case 465: case 465:
email.setSslSmtpPort(Integer.toString(port)); email.setSslSmtpPort(Integer.toString(port));

View File

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

View File

@ -36,10 +36,23 @@ public class MessageFileHandlerProvider {
* @return the message file handler * @return the message file handler
*/ */
public MessageFileHandler initializeHandler(Function<String, String> pathBuilder) { 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); String language = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
return new MessageFileHandler( return new MessageFileHandler(
initializeFile(language, pathBuilder), 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) { File initializeFile(String language, Function<String, String> pathBuilder) {
String filePath = pathBuilder.apply(language); String filePath = pathBuilder.apply(language);
File file = new File(dataFolder, filePath); 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; return file;
} }

View File

@ -17,7 +17,7 @@ public enum MessageKey {
/** AntiBot protection mode is enabled! You have to wait some minutes before joining the server. */ /** AntiBot protection mode is enabled! You have to wait some minutes before joining the server. */
KICK_ANTIBOT("kick_antibot"), KICK_ANTIBOT("kick_antibot"),
/** Can't find the requested user in the database! */ /** This user isn't registered! */
UNKNOWN_USER("unknown_user"), UNKNOWN_USER("unknown_user"),
/** Your quit location was unsafe, you have been teleported to the world's spawnpoint. */ /** Your quit location was unsafe, you have been teleported to the world's spawnpoint. */
@ -26,7 +26,7 @@ public enum MessageKey {
/** You're not logged in! */ /** You're not logged in! */
NOT_LOGGED_IN("not_logged_in"), NOT_LOGGED_IN("not_logged_in"),
/** Usage: /login &lt;password> */ /** Usage: /login &lt;password&gt; */
USAGE_LOGIN("usage_log"), USAGE_LOGIN("usage_log"),
/** Wrong password! */ /** Wrong password! */
@ -56,19 +56,19 @@ public enum MessageKey {
/** An unexpected error occurred, please contact an administrator! */ /** An unexpected error occurred, please contact an administrator! */
ERROR("error"), ERROR("error"),
/** Please, login with the command "/login &lt;password>" */ /** Please, login with the command: /login &lt;password&gt; */
LOGIN_MESSAGE("login_msg"), LOGIN_MESSAGE("login_msg"),
/** Please, register to the server with the command "/register &lt;password> &lt;ConfirmPassword>" */ /** Please, register to the server with the command: /register &lt;password&gt; &lt;ConfirmPassword&gt; */
REGISTER_MESSAGE("reg_msg"), REGISTER_MESSAGE("reg_msg"),
/** You have exceeded the maximum number of registrations (%reg_count/%max_acc %reg_names) for your connection! */ /** 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"), MAX_REGISTER_EXCEEDED("max_reg", "%max_acc", "%reg_count", "%reg_names"),
/** Usage: /register &lt;password> &lt;ConfirmPassword> */ /** Usage: /register &lt;password&gt; &lt;ConfirmPassword&gt; */
USAGE_REGISTER("usage_reg"), USAGE_REGISTER("usage_reg"),
/** Usage: /unregister &lt;password> */ /** Usage: /unregister &lt;password&gt; */
USAGE_UNREGISTER("usage_unreg"), USAGE_UNREGISTER("usage_unreg"),
/** Password changed successfully! */ /** Password changed successfully! */
@ -95,7 +95,7 @@ public enum MessageKey {
/** You're already logged in! */ /** You're already logged in! */
ALREADY_LOGGED_IN_ERROR("logged_in"), ALREADY_LOGGED_IN_ERROR("logged_in"),
/** Logged-out successfully! */ /** Logged out successfully! */
LOGOUT_SUCCESS("logout"), LOGOUT_SUCCESS("logout"),
/** The same username is already playing on the server! */ /** The same username is already playing on the server! */
@ -113,7 +113,7 @@ public enum MessageKey {
/** Login timeout exceeded, you have been kicked from the server, please try again! */ /** Login timeout exceeded, you have been kicked from the server, please try again! */
LOGIN_TIMEOUT_ERROR("timeout"), LOGIN_TIMEOUT_ERROR("timeout"),
/** Usage: /changepassword &lt;oldPassword> &lt;newPassword> */ /** Usage: /changepassword &lt;oldPassword&gt; &lt;newPassword&gt; */
USAGE_CHANGE_PASSWORD("usage_changepassword"), USAGE_CHANGE_PASSWORD("usage_changepassword"),
/** Your username is either too short or too long! */ /** Your username is either too short or too long! */
@ -122,13 +122,13 @@ public enum MessageKey {
/** Your username contains illegal characters. Allowed chars: REG_EX */ /** Your username contains illegal characters. Allowed chars: REG_EX */
INVALID_NAME_CHARACTERS("regex", "REG_EX"), INVALID_NAME_CHARACTERS("regex", "REG_EX"),
/** Please add your email to your account with the command "/email add &lt;yourEmail> &lt;confirmEmail>" */ /** Please add your email to your account with the command: /email add &lt;yourEmail&gt; &lt;confirmEmail&gt; */
ADD_EMAIL_MESSAGE("add_email"), ADD_EMAIL_MESSAGE("add_email"),
/** Forgot your password? Please use the command "/email recovery &lt;yourEmail>" */ /** Forgot your password? Please use the command: /email recovery &lt;yourEmail&gt; */
FORGOT_PASSWORD_MESSAGE("recovery_email"), FORGOT_PASSWORD_MESSAGE("recovery_email"),
/** To login you have to solve a captcha code, please use the command "/captcha &lt;theCaptcha>" */ /** To login you have to solve a captcha code, please use the command: /captcha &lt;theCaptcha&gt; */
USAGE_CAPTCHA("usage_captcha", "<theCaptcha>"), USAGE_CAPTCHA("usage_captcha", "<theCaptcha>"),
/** Wrong captcha, please type "/captcha THE_CAPTCHA" into the chat! */ /** Wrong captcha, please type "/captcha THE_CAPTCHA" into the chat! */
@ -143,13 +143,13 @@ public enum MessageKey {
/** The server is full, try again later! */ /** The server is full, try again later! */
KICK_FULL_SERVER("kick_fullserver"), KICK_FULL_SERVER("kick_fullserver"),
/** Usage: /email add &lt;email> &lt;confirmEmail> */ /** Usage: /email add &lt;email&gt; &lt;confirmEmail&gt; */
USAGE_ADD_EMAIL("usage_email_add"), USAGE_ADD_EMAIL("usage_email_add"),
/** Usage: /email change &lt;oldEmail> &lt;newEmail> */ /** Usage: /email change &lt;oldEmail&gt; &lt;newEmail&gt; */
USAGE_CHANGE_EMAIL("usage_email_change"), USAGE_CHANGE_EMAIL("usage_email_change"),
/** Usage: /email recovery &lt;Email> */ /** Usage: /email recovery &lt;Email&gt; */
USAGE_RECOVER_EMAIL("usage_email_recovery"), USAGE_RECOVER_EMAIL("usage_email_recovery"),
/** Invalid new email, try again! */ /** Invalid new email, try again! */
@ -224,8 +224,36 @@ public enum MessageKey {
/** A recovery code to reset your password has been sent to your email. */ /** A recovery code to reset your password has been sent to your email. */
RECOVERY_CODE_SENT("recovery_code_sent"), RECOVERY_CODE_SENT("recovery_code_sent"),
/** The recovery code is not correct! Use /email recovery [email] to generate a new one */ /** The recovery code is not correct! Use "/email recovery [email]" to generate a new one */
INCORRECT_RECOVERY_CODE("recovery_code_incorrect"); INCORRECT_RECOVERY_CODE("recovery_code_incorrect"),
/** 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 key;
private String[] tags; private String[] tags;

View File

@ -1,11 +1,15 @@
package fr.xephi.authme.message; package fr.xephi.authme.message;
import com.google.common.collect.ImmutableMap;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.util.expiring.Duration;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/** /**
* Class for retrieving and sending translatable messages to players. * 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 // Custom Authme tag replaced to new line
private static final String NEWLINE_TAG = "%nl%"; 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 final MessageFileHandlerProvider messageFileHandlerProvider;
private MessageFileHandler messageFileHandler; private MessageFileHandler messageFileHandler;
@ -71,6 +89,22 @@ public class Messages implements Reloadable {
return message.split("\n"); 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. * Retrieve the message from the text file.
* *
@ -107,7 +141,7 @@ public class Messages implements Reloadable {
@Override @Override
public void reload() { public void reload() {
this.messageFileHandler = messageFileHandlerProvider this.messageFileHandler = messageFileHandlerProvider
.initializeHandler(lang -> "messages/messages_" + lang + ".yml"); .initializeHandler(lang -> "messages/messages_" + lang + ".yml", "/authme messages");
} }
private static String formatMessage(String message) { private static String formatMessage(String message) {

View File

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

View File

@ -5,17 +5,24 @@ import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.data.limbo.LimboPlayer;
import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.settings.Settings; 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.PluginSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.inject.Inject; 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. * 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 { public class AuthGroupHandler implements Reloadable {
@ -28,7 +35,6 @@ public class AuthGroupHandler implements Reloadable {
@Inject @Inject
private LimboCache limboCache; private LimboCache limboCache;
private String unloggedInGroup;
private String unregisteredGroup; private String unregisteredGroup;
private String registeredGroup; 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 player the player
* @param group The group type. * @param groupType the group type
*
* @return True if succeeded, false otherwise. False is also returned if groups aren't supported
* with the current permissions system.
*/ */
public boolean setGroup(Player player, AuthGroupType group) { public void setGroup(Player player, AuthGroupType groupType) {
if (!useAuthGroups()) {
return;
}
String primaryGroup = Optional
.ofNullable(limboCache.getPlayerData(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 // Check whether the permissions check is enabled
if (!settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) { if (!settings.getProperty(PluginSettings.ENABLE_PERMISSION_CHECK)) {
return false; 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!"); ConsoleLogger.warning("The current permissions system doesn't have group support, unable to set group!");
return false; return false;
} }
return true;
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);
} }
@Override @Override
@PostConstruct @PostConstruct
public void reload() { public void reload() {
unloggedInGroup = settings.getProperty(SecuritySettings.UNLOGGEDIN_GROUP); unregisteredGroup = settings.getProperty(PluginSettings.UNREGISTERED_GROUP);
unregisteredGroup = settings.getProperty(HooksSettings.UNREGISTERED_GROUP); registeredGroup = settings.getProperty(PluginSettings.REGISTERED_GROUP);
registeredGroup = settings.getProperty(HooksSettings.REGISTERED_GROUP);
} }
} }

View File

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

View File

@ -19,8 +19,8 @@ import org.bukkit.plugin.PluginManager;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.ArrayList; import java.util.Collection;
import java.util.List; import java.util.Collections;
/** /**
* <p> * <p>
@ -255,12 +255,12 @@ public class PermissionsManager implements Reloadable {
* *
* @param player The player. * @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 no permissions system is used, return an empty list
if (!isEnabled()) if (!isEnabled())
return new ArrayList<>(); return Collections.emptyList();
return handler.getGroups(player); return handler.getGroups(player);
} }
@ -289,7 +289,7 @@ public class PermissionsManager implements Reloadable {
* @return True if the player is in the specified group, false otherwise. * @return True if the player is in the specified group, false otherwise.
* False is also returned if groups aren't supported by the used permissions system. * False is also returned if groups aren't supported by the used permissions system.
*/ */
public boolean inGroup(Player player, String groupName) { public boolean isInGroup(Player player, String groupName) {
// If no permissions system is used, return false // If no permissions system is used, return false
if (!isEnabled()) if (!isEnabled())
return false; return false;
@ -307,42 +307,12 @@ public class PermissionsManager implements Reloadable {
* False is also returned if this feature isn't supported for the current permissions system. * False is also returned if this feature isn't supported for the current permissions system.
*/ */
public boolean addGroup(Player player, String groupName) { public boolean addGroup(Player player, String groupName) {
if (StringUtils.isEmpty(groupName)) { if (!isEnabled() || StringUtils.isEmpty(groupName)) {
return false; return false;
} }
// If no permissions system is used, return false
if (!isEnabled()) {
return false;
}
return handler.addToGroup(player, groupName); 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. * Remove the permission group of a player, if supported.
* *
@ -352,8 +322,7 @@ public class PermissionsManager implements Reloadable {
* @return True if succeed, false otherwise. * @return True if succeed, false otherwise.
* False is also returned if this feature isn't supported for the current permissions system. * False is also returned if this feature isn't supported for the current permissions system.
*/ */
public boolean removeGroup(Player player, String groupName) { public boolean removeGroups(Player player, String groupName) {
// If no permissions system is used, return false
if (!isEnabled()) if (!isEnabled())
return false; return false;
@ -369,16 +338,18 @@ public class PermissionsManager implements Reloadable {
* @return True if succeed, false otherwise. * @return True if succeed, false otherwise.
* False is also returned if this feature isn't supported for the current permissions system. * False is also returned if this feature isn't supported for the current permissions system.
*/ */
public boolean removeGroups(Player player, List<String> groupNames) { public boolean removeGroups(Player player, String... groupNames) {
// If no permissions system is used, return false // If no permissions system is used, return false
if (!isEnabled()) if (!isEnabled())
return false; return false;
// Add each group to the user // Add each group to the user
boolean result = true; boolean result = true;
for (String groupName : groupNames) for (String groupName : groupNames) {
if (!removeGroup(player, groupName)) if (!handler.removeFromGroup(player, groupName)) {
result = false; result = false;
}
}
// Return the result // Return the result
return result; return result;
@ -402,41 +373,6 @@ public class PermissionsManager implements Reloadable {
return handler.setGroup(player, groupName); 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. * 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 * Systems like Essentials GroupManager don't allow all groups to be removed from a player, thus the user will stay
@ -453,9 +389,9 @@ public class PermissionsManager implements Reloadable {
return false; return false;
// Get a list of current groups // Get a list of current groups
List<String> groupNames = getGroups(player); Collection<String> groupNames = getGroups(player);
// Remove each group // Remove each group
return removeGroups(player, groupNames); return removeGroups(player, groupNames.toArray(new String[groupNames.size()]));
} }
} }

View File

@ -29,7 +29,12 @@ public enum PlayerStatePermission implements PermissionNode {
/** /**
* Permission to bypass the purging process. * 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. * The permission node.

View File

@ -9,6 +9,12 @@ import org.bukkit.entity.Player;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; 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 { public class BPermissionsHandler implements PermissionHandler {
@Override @Override
@ -49,19 +55,6 @@ public class BPermissionsHandler implements PermissionHandler {
return Arrays.asList(ApiLayer.getGroups(player.getWorld().getName(), CalculableType.USER, player.getName())); 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 @Override
public PermissionsSystemType getPermissionSystem() { public PermissionsSystemType getPermissionSystem() {
return PermissionsSystemType.B_PERMISSIONS; return PermissionsSystemType.B_PERMISSIONS;

View File

@ -2,9 +2,10 @@ package fr.xephi.authme.permission.handlers;
import fr.xephi.authme.permission.PermissionNode; import fr.xephi.authme.permission.PermissionNode;
import fr.xephi.authme.permission.PermissionsSystemType; import fr.xephi.authme.permission.PermissionsSystemType;
import fr.xephi.authme.util.Utils;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.util.List; import java.util.Collection;
public interface PermissionHandler { public interface PermissionHandler {
@ -48,7 +49,9 @@ public interface PermissionHandler {
* @return True if the player is in the specified group, false otherwise. * @return True if the player is in the specified group, false otherwise.
* False is also returned if groups aren't supported by the used permissions system. * False is also returned if groups aren't supported by the used permissions system.
*/ */
boolean isInGroup(Player player, String group); default boolean isInGroup(Player player, String group) {
return getGroups(player).contains(group);
}
/** /**
* Remove the permission group of a player, if supported. * 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. * @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. * 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. * @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. * Get the permission system that is being used.

View File

@ -12,6 +12,11 @@ import org.bukkit.plugin.PluginManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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 { public class PermissionsBukkitHandler implements PermissionHandler {
private PermissionsPlugin permissionsBukkitInstance; private PermissionsPlugin permissionsBukkitInstance;
@ -26,7 +31,8 @@ public class PermissionsBukkitHandler implements PermissionHandler {
@Override @Override
public boolean addToGroup(Player player, String group) { 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 @Override
@ -39,46 +45,27 @@ public class PermissionsBukkitHandler implements PermissionHandler {
return false; return false;
} }
@Override
public boolean isInGroup(Player player, String group) {
List<String> groupNames = getGroups(player);
return groupNames.contains(group);
}
@Override @Override
public boolean removeFromGroup(Player player, String group) { 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 @Override
public boolean setGroup(Player player, String group) { 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 @Override
public List<String> getGroups(Player player) { public List<String> getGroups(Player player) {
List<String> groups = new ArrayList<String>(); List<String> groups = new ArrayList<>();
for (Group group : permissionsBukkitInstance.getGroups(player.getUniqueId())) { for (Group group : permissionsBukkitInstance.getGroups(player.getUniqueId())) {
groups.add(group.getName()); groups.add(group.getName());
} }
return groups; 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 @Override
public PermissionsSystemType getPermissionSystem() { public PermissionsSystemType getPermissionSystem() {
return PermissionsSystemType.PERMISSIONS_BUKKIT; return PermissionsSystemType.PERMISSIONS_BUKKIT;

View File

@ -10,6 +10,12 @@ import ru.tehkode.permissions.bukkit.PermissionsEx;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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 { public class PermissionsExHandler implements PermissionHandler {
private PermissionManager permissionManager; private PermissionManager permissionManager;
@ -72,17 +78,6 @@ public class PermissionsExHandler implements PermissionHandler {
return user.getParentIdentifiers(null); 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 @Override
public PermissionsSystemType getPermissionSystem() { public PermissionsSystemType getPermissionSystem() {
return PermissionsSystemType.PERMISSIONS_EX; return PermissionsSystemType.PERMISSIONS_EX;

View File

@ -10,6 +10,12 @@ import org.bukkit.plugin.RegisteredServiceProvider;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
/**
* Handler for permissions via Vault.
*
* @see <a href="https://dev.bukkit.org/projects/vault">Vault Bukkit page</a>
* @see <a href="https://github.com/milkbowl/Vault">Vault on Github</a>
*/
public class VaultHandler implements PermissionHandler { public class VaultHandler implements PermissionHandler {
private Permission vaultProvider; private Permission vaultProvider;

View File

@ -6,10 +6,15 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService; import org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService;
import java.util.ArrayList; import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
/**
* Handler for zPermissions.
*
* @see <a href="https://dev.bukkit.org/projects/zpermissions">zPermissions Bukkit page</a>
* @see <a href="https://github.com/ZerothAngel/zPermissions">zPermissions on Github</a>
*/
public class ZPermissionsHandler implements PermissionHandler { public class ZPermissionsHandler implements PermissionHandler {
private ZPermissionsService zPermissionsService; private ZPermissionsService zPermissionsService;
@ -25,7 +30,8 @@ public class ZPermissionsHandler implements PermissionHandler {
@Override @Override
public boolean addToGroup(Player player, String group) { public boolean addToGroup(Player player, String group) {
return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " addgroup " + group); return Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"permissions player " + player.getName() + " addgroup " + group);
} }
@Override @Override
@ -36,31 +42,29 @@ public class ZPermissionsHandler implements PermissionHandler {
@Override @Override
public boolean hasPermissionOffline(String name, PermissionNode node) { public boolean hasPermissionOffline(String name, PermissionNode node) {
Map<String, Boolean> perms = zPermissionsService.getPlayerPermissions(null, null, name); Map<String, Boolean> perms = zPermissionsService.getPlayerPermissions(null, null, name);
if (perms.containsKey(node.getNode())) if (perms.containsKey(node.getNode())) {
return perms.get(node.getNode()); return perms.get(node.getNode());
else } else {
return false; return false;
} }
@Override
public boolean isInGroup(Player player, String group) {
return getGroups(player).contains(group);
} }
@Override @Override
public boolean removeFromGroup(Player player, String group) { public boolean removeFromGroup(Player player, String group) {
return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " removegroup " + group); return Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"permissions player " + player.getName() + " removegroup " + group);
} }
@Override @Override
public boolean setGroup(Player player, String group) { public boolean setGroup(Player player, String group) {
return Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "permissions player " + player.getName() + " setgroup " + group); return Bukkit.dispatchCommand(Bukkit.getConsoleSender(),
"permissions player " + player.getName() + " setgroup " + group);
} }
@Override @Override
public List<String> getGroups(Player player) { public Collection<String> getGroups(Player player) {
// TODO Gnat008 20160631: Use UUID not name? // TODO Gnat008 20160631: Use UUID not name?
return new ArrayList<String>(zPermissionsService.getPlayerGroups(player.getName())); return zPermissionsService.getPlayerGroups(player.getName());
} }
@Override @Override

View File

@ -1,6 +1,5 @@
package fr.xephi.authme.process.join; package fr.xephi.authme.process.join;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.SessionManager; import fr.xephi.authme.data.SessionManager;
import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerAuth;
@ -8,30 +7,32 @@ import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.ProtectInventoryEvent; import fr.xephi.authme.events.ProtectInventoryEvent;
import fr.xephi.authme.service.PluginHookService;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.permission.AuthGroupType;
import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.permission.PlayerStatePermission;
import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.AsynchronousProcess;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.process.login.AsynchronousLogin; import fr.xephi.authme.process.login.AsynchronousLogin;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.PluginHookService;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.commandconfig.CommandManager;
import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.HooksSettings;
import fr.xephi.authme.settings.properties.PluginSettings; import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.task.LimboPlayerTaskManager;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import javax.inject.Inject; import javax.inject.Inject;
import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN;
import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND; import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND;
import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN;
/** /**
* Asynchronous process for when a player joins. * Asynchronous process for when a player joins.
@ -39,7 +40,7 @@ import static fr.xephi.authme.service.BukkitService.TICKS_PER_SECOND;
public class AsynchronousJoin implements AsynchronousProcess { public class AsynchronousJoin implements AsynchronousProcess {
@Inject @Inject
private AuthMe plugin; private Server server;
@Inject @Inject
private DataSource database; private DataSource database;
@ -71,6 +72,9 @@ public class AsynchronousJoin implements AsynchronousProcess {
@Inject @Inject
private CommandManager commandManager; private CommandManager commandManager;
@Inject
private ValidationService validationService;
AsynchronousJoin() { AsynchronousJoin() {
} }
@ -91,13 +95,13 @@ public class AsynchronousJoin implements AsynchronousProcess {
pluginHookService.setEssentialsSocialSpyStatus(player, false); pluginHookService.setEssentialsSocialSpyStatus(player, false);
} }
if (isNameRestricted(name, ip, player.getAddress().getHostName())) { if (!validationService.fulfillsNameRestrictions(player)) {
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() { bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() {
@Override @Override
public void run() { public void run() {
player.kickPlayer(service.retrieveSingleMessage(MessageKey.NOT_OWNER_ERROR)); player.kickPlayer(service.retrieveSingleMessage(MessageKey.NOT_OWNER_ERROR));
if (service.getProperty(RestrictionSettings.BAN_UNKNOWN_IP)) { if (service.getProperty(RestrictionSettings.BAN_UNKNOWN_IP)) {
plugin.getServer().banIP(ip); server.banIP(ip);
} }
} }
}); });
@ -112,7 +116,7 @@ public class AsynchronousJoin implements AsynchronousProcess {
if (isAuthAvailable) { if (isAuthAvailable) {
limboCache.addPlayerData(player); limboCache.addPlayerData(player);
service.setGroup(player, AuthGroupType.NOT_LOGGED_IN); service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED);
// Protect inventory // Protect inventory
if (service.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) { if (service.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) {
@ -130,14 +134,16 @@ public class AsynchronousJoin implements AsynchronousProcess {
PlayerAuth auth = database.getAuth(name); PlayerAuth auth = database.getAuth(name);
database.setUnlogged(name); database.setUnlogged(name);
playerCache.removePlayer(name); playerCache.removePlayer(name);
if (auth != null && auth.getIp().equals(ip)) { if (auth != null) {
if (auth.getIp().equals(ip)) {
service.send(player, MessageKey.SESSION_RECONNECTION); service.send(player, MessageKey.SESSION_RECONNECTION);
bukkitService.runTaskOptionallyAsync(() -> asynchronousLogin.forceLogin(player)); bukkitService.runTaskOptionallyAsync(() -> asynchronousLogin.forceLogin(player));
return; return;
} else if (service.getProperty(PluginSettings.SESSIONS_EXPIRE_ON_IP_CHANGE)) { } else {
service.send(player, MessageKey.SESSION_EXPIRED); service.send(player, MessageKey.SESSION_EXPIRED);
} }
} }
}
} else { } else {
// Not Registered. Delete old data, load default one. // Not Registered. Delete old data, load default one.
limboCache.deletePlayerData(player); limboCache.deletePlayerData(player);
@ -178,36 +184,6 @@ public class AsynchronousJoin implements AsynchronousProcess {
limboPlayerTaskManager.registerMessageTask(name, isAuthAvailable); limboPlayerTaskManager.registerMessageTask(name, isAuthAvailable);
} }
/**
* Returns whether the name is restricted based on the restriction settings.
*
* @param name The name to check
* @param ip The IP address of the player
* @param domain The hostname of the IP address
*
* @return True if the name is restricted (IP/domain is not allowed for the given name),
* false if the restrictions are met or if the name has no restrictions to it
*/
private boolean isNameRestricted(String name, String ip, String domain) {
if (!service.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)) {
return false;
}
boolean nameFound = false;
for (String entry : service.getProperty(RestrictionSettings.ALLOWED_RESTRICTED_USERS)) {
String[] args = entry.split(";");
String testName = args[0];
String testIp = args[1];
if (testName.equalsIgnoreCase(name)) {
nameFound = true;
if ((ip != null && testIp.equals(ip)) || (domain != null && testIp.equalsIgnoreCase(domain))) {
return false;
}
}
}
return nameFound;
}
/** /**
* Checks whether the maximum number of accounts has been exceeded for the given IP address (according to * Checks whether the maximum number of accounts has been exceeded for the given IP address (according to
* settings and permissions). If this is the case, the player is kicked. * settings and permissions). If this is the case, the player is kicked.

View File

@ -12,7 +12,6 @@ import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.AdminPermission; import fr.xephi.authme.permission.AdminPermission;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.permission.PlayerPermission;
import fr.xephi.authme.permission.PlayerStatePermission; import fr.xephi.authme.permission.PlayerStatePermission;
import fr.xephi.authme.process.AsynchronousProcess; import fr.xephi.authme.process.AsynchronousProcess;
@ -46,9 +45,6 @@ public class AsynchronousLogin implements AsynchronousProcess {
@Inject @Inject
private CommonService service; private CommonService service;
@Inject
private PermissionsManager permissionsManager;
@Inject @Inject
private PlayerCache playerCache; private PlayerCache playerCache;
@ -269,8 +265,8 @@ public class AsynchronousLogin implements AsynchronousProcess {
} }
bukkitService.dispatchConsoleCommand(command bukkitService.dispatchConsoleCommand(command
.replaceAll("%playername%", player.getName()) .replace("%playername%", player.getName())
.replaceAll("%playerip%", ip) .replace("%playerip%", ip)
); );
} }
@ -300,10 +296,10 @@ public class AsynchronousLogin implements AsynchronousProcess {
for (Player onlinePlayer : bukkitService.getOnlinePlayers()) { for (Player onlinePlayer : bukkitService.getOnlinePlayers()) {
if (onlinePlayer.getName().equalsIgnoreCase(player.getName()) if (onlinePlayer.getName().equalsIgnoreCase(player.getName())
&& permissionsManager.hasPermission(onlinePlayer, PlayerPermission.SEE_OWN_ACCOUNTS)) { && service.hasPermission(onlinePlayer, PlayerPermission.SEE_OWN_ACCOUNTS)) {
service.send(onlinePlayer, MessageKey.ACCOUNTS_OWNED_SELF, Integer.toString(auths.size())); service.send(onlinePlayer, MessageKey.ACCOUNTS_OWNED_SELF, Integer.toString(auths.size()));
onlinePlayer.sendMessage(message); onlinePlayer.sendMessage(message);
} else if (permissionsManager.hasPermission(onlinePlayer, AdminPermission.SEE_OTHER_ACCOUNTS)) { } else if (service.hasPermission(onlinePlayer, AdminPermission.SEE_OTHER_ACCOUNTS)) {
service.send(onlinePlayer, MessageKey.ACCOUNTS_OWNED_OTHER, service.send(onlinePlayer, MessageKey.ACCOUNTS_OWNED_OTHER,
player.getName(), Integer.toString(auths.size())); player.getName(), Integer.toString(auths.size()));
onlinePlayer.sendMessage(message); onlinePlayer.sendMessage(message);
@ -323,7 +319,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
boolean hasReachedMaxLoggedInPlayersForIp(Player player, String ip) { boolean hasReachedMaxLoggedInPlayersForIp(Player player, String ip) {
// Do not perform the check if player has multiple accounts permission or if IP is localhost // Do not perform the check if player has multiple accounts permission or if IP is localhost
if (service.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP) <= 0 if (service.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP) <= 0
|| permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS) || service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)
|| "127.0.0.1".equalsIgnoreCase(ip) || "127.0.0.1".equalsIgnoreCase(ip)
|| "localhost".equalsIgnoreCase(ip)) { || "localhost".equalsIgnoreCase(ip)) {
return false; return false;

View File

@ -1,6 +1,5 @@
package fr.xephi.authme.process.login; package fr.xephi.authme.process.login;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.data.limbo.LimboPlayer; import fr.xephi.authme.data.limbo.LimboPlayer;
@ -8,28 +7,26 @@ import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.LoginEvent; import fr.xephi.authme.events.LoginEvent;
import fr.xephi.authme.events.RestoreInventoryEvent; import fr.xephi.authme.events.RestoreInventoryEvent;
import fr.xephi.authme.listener.PlayerListener; import fr.xephi.authme.listener.PlayerListener;
import fr.xephi.authme.permission.AuthGroupType;
import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.process.SynchronousProcess;
import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.BungeeService; import fr.xephi.authme.service.BungeeService;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.TeleportationService; import fr.xephi.authme.service.TeleportationService;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.WelcomeMessageConfiguration;
import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.commandconfig.CommandManager;
import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.List;
import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN; import static fr.xephi.authme.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN;
public class ProcessSyncPlayerLogin implements SynchronousProcess { public class ProcessSyncPlayerLogin implements SynchronousProcess {
@Inject
private AuthMe plugin;
@Inject @Inject
private BungeeService bungeeService; private BungeeService bungeeService;
@ -39,9 +36,6 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
@Inject @Inject
private BukkitService bukkitService; private BukkitService bukkitService;
@Inject
private PluginManager pluginManager;
@Inject @Inject
private TeleportationService teleportationService; private TeleportationService teleportationService;
@ -52,14 +46,17 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
private CommandManager commandManager; private CommandManager commandManager;
@Inject @Inject
private Settings settings; private CommonService commonService;
@Inject
private WelcomeMessageConfiguration welcomeMessageConfiguration;
ProcessSyncPlayerLogin() { ProcessSyncPlayerLogin() {
} }
private void restoreInventory(Player player) { private void restoreInventory(Player player) {
RestoreInventoryEvent event = new RestoreInventoryEvent(player); RestoreInventoryEvent event = new RestoreInventoryEvent(player);
pluginManager.callEvent(event); bukkitService.callEvent(event);
if (!event.isCancelled()) { if (!event.isCancelled()) {
player.updateInventory(); player.updateInventory();
} }
@ -76,8 +73,9 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
// do we really need to use location from database for now? // do we really need to use location from database for now?
// because LimboCache#restoreData teleport player to last location. // because LimboCache#restoreData teleport player to last location.
} }
commonService.setGroup(player, AuthGroupType.LOGGED_IN);
if (settings.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) { if (commonService.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) {
restoreInventory(player); restoreInventory(player);
} }
@ -94,7 +92,7 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
} }
} }
if (settings.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) { if (commonService.getProperty(RegistrationSettings.APPLY_BLIND_EFFECT)) {
player.removePotionEffect(PotionEffectType.BLINDNESS); player.removePotionEffect(PotionEffectType.BLINDNESS);
} }
@ -103,15 +101,12 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
player.saveData(); player.saveData();
// Login is done, display welcome message // Login is done, display welcome message
if (settings.getProperty(RegistrationSettings.USE_WELCOME_MESSAGE)) { List<String> welcomeMessage = welcomeMessageConfiguration.getWelcomeMessage(player);
if (settings.getProperty(RegistrationSettings.BROADCAST_WELCOME_MESSAGE)) { if (commonService.getProperty(RegistrationSettings.USE_WELCOME_MESSAGE)) {
for (String s : settings.getWelcomeMessage()) { if (commonService.getProperty(RegistrationSettings.BROADCAST_WELCOME_MESSAGE)) {
Bukkit.getServer().broadcastMessage(plugin.replaceAllInfo(s, player)); welcomeMessage.forEach(bukkitService::broadcastMessage);
}
} else { } else {
for (String s : settings.getWelcomeMessage()) { welcomeMessage.forEach(player::sendMessage);
player.sendMessage(plugin.replaceAllInfo(s, player));
}
} }
} }

View File

@ -77,7 +77,7 @@ public class ProcessSynchronousPlayerLogout implements SynchronousProcess {
} }
// Set player's data to unauthenticated // Set player's data to unauthenticated
service.setGroup(player, AuthGroupType.NOT_LOGGED_IN); service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED);
player.setOp(false); player.setOp(false);
player.setAllowFlight(false); player.setAllowFlight(false);
// Remove speed // Remove speed

View File

@ -1,6 +1,6 @@
package fr.xephi.authme.process.quit; package fr.xephi.authme.process.quit;
import fr.xephi.authme.data.backup.LimboPlayerStorage; import fr.xephi.authme.data.limbo.LimboPlayerStorage;
import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.process.SynchronousProcess;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;

View File

@ -3,9 +3,8 @@ package fr.xephi.authme.process.register;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.permission.AuthGroupType;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.process.SynchronousProcess;
import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.task.LimboPlayerTaskManager;
import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -25,12 +24,10 @@ public class ProcessSyncEmailRegister implements SynchronousProcess {
} }
public void processEmailRegister(Player player) { public void processEmailRegister(Player player) {
final String name = player.getName().toLowerCase(); service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED);
if (!service.getProperty(HooksSettings.REGISTERED_GROUP).isEmpty()) {
service.setGroup(player, AuthGroupType.REGISTERED);
}
service.send(player, MessageKey.ACCOUNT_NOT_ACTIVATED); service.send(player, MessageKey.ACCOUNT_NOT_ACTIVATED);
final String name = player.getName().toLowerCase();
limboPlayerTaskManager.registerTimeoutTask(player); limboPlayerTaskManager.registerTimeoutTask(player);
limboPlayerTaskManager.registerMessageTask(name, true); limboPlayerTaskManager.registerMessageTask(name, true);

View File

@ -4,12 +4,11 @@ import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboCache; import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.AuthGroupType; import fr.xephi.authme.permission.AuthGroupType;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.process.SynchronousProcess; import fr.xephi.authme.process.SynchronousProcess;
import fr.xephi.authme.service.BungeeService; import fr.xephi.authme.service.BungeeService;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.commandconfig.CommandManager; import fr.xephi.authme.settings.commandconfig.CommandManager;
import fr.xephi.authme.settings.properties.EmailSettings; import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.HooksSettings;
import fr.xephi.authme.settings.properties.RegistrationSettings; import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.task.LimboPlayerTaskManager; import fr.xephi.authme.task.LimboPlayerTaskManager;
import fr.xephi.authme.util.PlayerUtils; import fr.xephi.authme.util.PlayerUtils;
@ -56,10 +55,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess {
} }
public void processPasswordRegister(Player player) { public void processPasswordRegister(Player player) {
if (!service.getProperty(HooksSettings.REGISTERED_GROUP).isEmpty()) { service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED);
service.setGroup(player, AuthGroupType.REGISTERED);
}
service.send(player, MessageKey.REGISTER_SUCCESS); service.send(player, MessageKey.REGISTER_SUCCESS);
if (!service.getProperty(EmailSettings.MAIL_ACCOUNT).isEmpty()) { if (!service.getProperty(EmailSettings.MAIL_ACCOUNT).isEmpty()) {

View File

@ -2,7 +2,7 @@ package fr.xephi.authme.process.register.executors;
import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource; 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.message.MessageKey;
import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.SyncProcessManager; import fr.xephi.authme.process.SyncProcessManager;
@ -15,8 +15,8 @@ import org.bukkit.entity.Player;
import javax.inject.Inject; import javax.inject.Inject;
import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
import static fr.xephi.authme.permission.PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS; import static fr.xephi.authme.permission.PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS;
import static fr.xephi.authme.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH; import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
/** /**
@ -34,7 +34,7 @@ class EmailRegisterExecutorProvider {
private CommonService commonService; private CommonService commonService;
@Inject @Inject
private SendMailSSL sendMailSsl; private EmailService emailService;
@Inject @Inject
private SyncProcessManager syncProcessManager; private SyncProcessManager syncProcessManager;
@ -80,7 +80,7 @@ class EmailRegisterExecutorProvider {
@Override @Override
public void executePostPersistAction() { public void executePostPersistAction() {
boolean couldSendMail = sendMailSsl.sendPasswordMail(player.getName(), email, password); boolean couldSendMail = emailService.sendPasswordMail(player.getName(), email, password);
if (couldSendMail) { if (couldSendMail) {
syncProcessManager.processSyncEmailRegister(player); syncProcessManager.processSyncEmailRegister(player);
} else { } else {

View File

@ -56,10 +56,10 @@ class PasswordRegisterExecutorProvider {
/** Registration executor for password registration. */ /** Registration executor for password registration. */
class PasswordRegisterExecutor implements RegistrationExecutor { class PasswordRegisterExecutor implements RegistrationExecutor {
protected final Player player; private final Player player;
private final String password; private final String password;
private final String email; private final String email;
protected HashedPassword hashedPassword; private HashedPassword hashedPassword;
/** /**
* Constructor. * Constructor.
@ -105,6 +105,14 @@ class PasswordRegisterExecutorProvider {
} }
syncProcessManager.processSyncPasswordRegister(player); syncProcessManager.processSyncPasswordRegister(player);
} }
protected Player getPlayer() {
return player;
}
protected HashedPassword getHashedPassword() {
return hashedPassword;
}
} }
/** Executor for password registration via API call. */ /** Executor for password registration via API call. */
@ -147,8 +155,9 @@ class PasswordRegisterExecutorProvider {
public void executePostPersistAction() { public void executePostPersistAction() {
super.executePostPersistAction(); super.executePostPersistAction();
String qrCodeUrl = TwoFactor.getQRBarcodeURL(player.getName(), Bukkit.getIp(), hashedPassword.getHash()); String hash = getHashedPassword().getHash();
commonService.send(player, MessageKey.TWO_FACTOR_CREATE, hashedPassword.getHash(), qrCodeUrl); String qrCodeUrl = TwoFactor.getQRBarcodeURL(getPlayer().getName(), Bukkit.getIp(), hash);
commonService.send(getPlayer(), MessageKey.TWO_FACTOR_CREATE, hash, qrCodeUrl);
} }
} }

View File

@ -1,9 +1,9 @@
package fr.xephi.authme.security; package fr.xephi.authme.security;
import ch.jalu.injector.Injector;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.PasswordEncryptionEvent; import fr.xephi.authme.events.PasswordEncryptionEvent;
import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.security.crypts.EncryptionMethod; import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.security.crypts.HashedPassword; import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
@ -29,7 +29,7 @@ public class PasswordSecurity implements Reloadable {
private PluginManager pluginManager; private PluginManager pluginManager;
@Inject @Inject
private Injector injector; private Factory<EncryptionMethod> hashAlgorithmFactory;
private HashAlgorithm algorithm; private HashAlgorithm algorithm;
private Collection<HashAlgorithm> legacyAlgorithms; private Collection<HashAlgorithm> legacyAlgorithms;
@ -154,7 +154,7 @@ public class PasswordSecurity implements Reloadable {
if (HashAlgorithm.CUSTOM.equals(algorithm) || HashAlgorithm.PLAINTEXT.equals(algorithm)) { if (HashAlgorithm.CUSTOM.equals(algorithm) || HashAlgorithm.PLAINTEXT.equals(algorithm)) {
return null; return null;
} }
return injector.newInstance(algorithm.getClazz()); return hashAlgorithmFactory.newInstance(algorithm.getClazz());
} }
private void hashPasswordForNewAlgorithm(String password, String playerName) { private void hashPasswordForNewAlgorithm(String password, String playerName) {

View File

@ -8,15 +8,15 @@ public abstract class SeparateSaltMethod implements EncryptionMethod {
@Override @Override
public abstract String computeHash(String password, String salt, String name); public abstract String computeHash(String password, String salt, String name);
@Override
public abstract String generateSalt();
@Override @Override
public HashedPassword computeHash(String password, String name) { public HashedPassword computeHash(String password, String name) {
String salt = generateSalt(); String salt = generateSalt();
return new HashedPassword(computeHash(password, salt, name), salt); return new HashedPassword(computeHash(password, salt, name), salt);
} }
@Override
public abstract String generateSalt();
@Override @Override
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
return hashedPassword.getHash().equals(computeHash(password, hashedPassword.getSalt(), null)); return hashedPassword.getHash().equals(computeHash(password, hashedPassword.getSalt(), null));

View File

@ -17,13 +17,13 @@ public abstract class UsernameSaltMethod implements EncryptionMethod {
public abstract HashedPassword computeHash(String password, String name); public abstract HashedPassword computeHash(String password, String name);
@Override @Override
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) { public String computeHash(String password, String salt, String name) {
return hashedPassword.getHash().equals(computeHash(password, name).getHash()); return computeHash(password, name).getHash();
} }
@Override @Override
public String computeHash(String password, String salt, String name) { public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
return computeHash(password, name).getHash(); return hashedPassword.getHash().equals(computeHash(password, name).getHash());
} }
@Override @Override

View File

@ -30,7 +30,6 @@ public class AntiBotService implements SettingsDependent {
// Settings // Settings
private int duration; private int duration;
private int sensibility; private int sensibility;
private int delay;
private int interval; private int interval;
// Service status // Service status
private AntiBotStatus antiBotStatus; private AntiBotStatus antiBotStatus;
@ -60,7 +59,6 @@ public class AntiBotService implements SettingsDependent {
// Load settings // Load settings
duration = settings.getProperty(ProtectionSettings.ANTIBOT_DURATION); duration = settings.getProperty(ProtectionSettings.ANTIBOT_DURATION);
sensibility = settings.getProperty(ProtectionSettings.ANTIBOT_SENSIBILITY); sensibility = settings.getProperty(ProtectionSettings.ANTIBOT_SENSIBILITY);
delay = settings.getProperty(ProtectionSettings.ANTIBOT_DELAY);
interval = settings.getProperty(ProtectionSettings.ANTIBOT_INTERVAL); interval = settings.getProperty(ProtectionSettings.ANTIBOT_INTERVAL);
// Stop existing protection // Stop existing protection
@ -77,6 +75,7 @@ public class AntiBotService implements SettingsDependent {
// Delay the schedule on first start // Delay the schedule on first start
if (startup) { if (startup) {
int delay = settings.getProperty(ProtectionSettings.ANTIBOT_DELAY);
bukkitService.scheduleSyncDelayedTask(enableTask, delay * TICKS_PER_SECOND); bukkitService.scheduleSyncDelayedTask(enableTask, delay * TICKS_PER_SECOND);
startup = false; startup = false;
} else { } else {

View File

@ -13,7 +13,6 @@ import org.bukkit.World;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask; import org.bukkit.scheduler.BukkitTask;
@ -172,7 +171,7 @@ public class BukkitService implements SettingsDependent {
* @return a BukkitTask that contains the id number * @return a BukkitTask that contains the id number
* @throws IllegalArgumentException if plugin is null * @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled * @throws IllegalStateException if this was already scheduled
* @see BukkitScheduler#runTaskTimer(Plugin, Runnable, long, long) * @see BukkitScheduler#runTaskTimer(org.bukkit.plugin.Plugin, Runnable, long, long)
*/ */
public BukkitTask runTaskTimer(BukkitRunnable task, long delay, long period) { public BukkitTask runTaskTimer(BukkitRunnable task, long delay, long period) {
return task.runTaskTimer(authMe, delay, period); return task.runTaskTimer(authMe, delay, period);

View File

@ -65,16 +65,6 @@ public class CommonService {
messages.send(sender, key, replacements); messages.send(sender, key, replacements);
} }
/**
* Retrieves a message.
*
* @param key the key of the message
* @return the message, split by line
*/
public String[] retrieveMessage(MessageKey key) {
return messages.retrieve(key);
}
/** /**
* Retrieves a message in one piece. * Retrieves a message in one piece.
* *
@ -101,10 +91,10 @@ public class CommonService {
* *
* @param player the player to process * @param player the player to process
* @param group the group to add the player to * @param group the group to add the player to
* @return true on success, false otherwise
*/ */
public boolean setGroup(Player player, AuthGroupType group) { // TODO ljacqu 20170304: Move this out of CommonService
return authGroupHandler.setGroup(player, group); public void setGroup(Player player, AuthGroupType group) {
authGroupHandler.setGroup(player, group);
} }
} }

View File

@ -1,38 +1,38 @@
package fr.xephi.authme.service; package fr.xephi.authme.service;
import com.google.common.annotations.VisibleForTesting; import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.SettingsDependent; import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.util.expiring.ExpiringMap;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Map; import java.util.concurrent.TimeUnit;
import java.util.concurrent.ConcurrentHashMap;
import static fr.xephi.authme.settings.properties.SecuritySettings.RECOVERY_CODE_HOURS_VALID; import static fr.xephi.authme.settings.properties.SecuritySettings.RECOVERY_CODE_HOURS_VALID;
import static fr.xephi.authme.util.Utils.MILLIS_PER_HOUR;
/** /**
* Manager for recovery codes. * Manager for recovery codes.
*/ */
public class RecoveryCodeService implements SettingsDependent { public class RecoveryCodeService implements SettingsDependent, HasCleanup {
private Map<String, ExpiringEntry> recoveryCodes = new ConcurrentHashMap<>();
private final ExpiringMap<String, String> recoveryCodes;
private int recoveryCodeLength; private int recoveryCodeLength;
private long recoveryCodeExpirationMillis; private int recoveryCodeExpiration;
@Inject @Inject
RecoveryCodeService(Settings settings) { RecoveryCodeService(Settings settings) {
reload(settings); recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH);
recoveryCodeExpiration = settings.getProperty(SecuritySettings.RECOVERY_CODE_HOURS_VALID);
recoveryCodes = new ExpiringMap<>(recoveryCodeExpiration, TimeUnit.HOURS);
} }
/** /**
* @return whether recovery codes are enabled or not * @return whether recovery codes are enabled or not
*/ */
public boolean isRecoveryCodeNeeded() { public boolean isRecoveryCodeNeeded() {
return recoveryCodeLength > 0 && recoveryCodeExpirationMillis > 0; return recoveryCodeLength > 0 && recoveryCodeExpiration > 0;
} }
/** /**
@ -43,7 +43,7 @@ public class RecoveryCodeService implements SettingsDependent {
*/ */
public String generateCode(String player) { public String generateCode(String player) {
String code = RandomStringUtils.generateHex(recoveryCodeLength); String code = RandomStringUtils.generateHex(recoveryCodeLength);
recoveryCodes.put(player, new ExpiringEntry(code, System.currentTimeMillis() + recoveryCodeExpirationMillis)); recoveryCodes.put(player, code);
return code; return code;
} }
@ -55,11 +55,8 @@ public class RecoveryCodeService implements SettingsDependent {
* @return true if the code matches and has not expired, false otherwise * @return true if the code matches and has not expired, false otherwise
*/ */
public boolean isCodeValid(String player, String code) { public boolean isCodeValid(String player, String code) {
ExpiringEntry entry = recoveryCodes.get(player); String storedCode = recoveryCodes.get(player);
if (entry != null) { return storedCode != null && storedCode.equals(code);
return code != null && code.equals(entry.getCode());
}
return false;
} }
/** /**
@ -74,26 +71,12 @@ public class RecoveryCodeService implements SettingsDependent {
@Override @Override
public void reload(Settings settings) { public void reload(Settings settings) {
recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH); recoveryCodeLength = settings.getProperty(SecuritySettings.RECOVERY_CODE_LENGTH);
recoveryCodeExpirationMillis = settings.getProperty(RECOVERY_CODE_HOURS_VALID) * MILLIS_PER_HOUR; recoveryCodeExpiration = settings.getProperty(RECOVERY_CODE_HOURS_VALID);
recoveryCodes.setExpiration(recoveryCodeExpiration, TimeUnit.HOURS);
} }
/** @Override
* Entry with an expiration. public void performCleanup() {
*/ recoveryCodes.removeExpiredEntries();
@VisibleForTesting
static final class ExpiringEntry {
private final String code;
private final long expiration;
ExpiringEntry(String code, long expiration) {
this.code = code;
this.expiration = expiration;
}
String getCode() {
return System.currentTimeMillis() < expiration ? code : null;
} }
} }
}

View File

@ -1,6 +1,9 @@
package fr.xephi.authme.service; package fr.xephi.authme.service;
import ch.jalu.configme.properties.Property; import ch.jalu.configme.properties.Property;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
@ -11,9 +14,10 @@ import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.ProtectionSettings; import fr.xephi.authme.settings.properties.ProtectionSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.settings.properties.SecuritySettings; import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.CollectionUtils; import fr.xephi.authme.util.PlayerUtils;
import fr.xephi.authme.util.Utils; import fr.xephi.authme.util.Utils;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.inject.Inject; import javax.inject.Inject;
@ -23,6 +27,8 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static fr.xephi.authme.util.StringUtils.isInsideString;
/** /**
* Validation service. * Validation service.
*/ */
@ -39,6 +45,7 @@ public class ValidationService implements Reloadable {
private Pattern passwordRegex; private Pattern passwordRegex;
private Set<String> unrestrictedNames; private Set<String> unrestrictedNames;
private Multimap<String, String> restrictedNames;
ValidationService() { ValidationService() {
} }
@ -49,6 +56,9 @@ public class ValidationService implements Reloadable {
passwordRegex = Utils.safePatternCompile(settings.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX)); passwordRegex = Utils.safePatternCompile(settings.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX));
// Use Set for more efficient contains() lookup // Use Set for more efficient contains() lookup
unrestrictedNames = new HashSet<>(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES)); unrestrictedNames = new HashSet<>(settings.getProperty(RestrictionSettings.UNRESTRICTED_NAMES));
restrictedNames = settings.getProperty(RestrictionSettings.ENABLE_RESTRICTED_USERS)
? loadNameRestrictions(settings.getProperty(RestrictionSettings.RESTRICTED_USERS))
: HashMultimap.create();
} }
/** /**
@ -116,9 +126,10 @@ public class ValidationService implements Reloadable {
} }
String countryCode = geoIpService.getCountryCode(hostAddress); String countryCode = geoIpService.getCountryCode(hostAddress);
return validateWhitelistAndBlacklist(countryCode, boolean isCountryAllowed = validateWhitelistAndBlacklist(countryCode,
ProtectionSettings.COUNTRIES_WHITELIST, ProtectionSettings.COUNTRIES_WHITELIST, ProtectionSettings.COUNTRIES_BLACKLIST);
ProtectionSettings.COUNTRIES_BLACKLIST); ConsoleLogger.debug("Country code `{0}` for `{1}` is allowed: {2}", countryCode, hostAddress, isCountryAllowed);
return isCountryAllowed;
} }
/** /**
@ -131,6 +142,24 @@ public class ValidationService implements Reloadable {
return unrestrictedNames.contains(name.toLowerCase()); return unrestrictedNames.contains(name.toLowerCase());
} }
/**
* Checks that the player meets any name restriction if present (IP/domain-based).
*
* @param player the player to check
* @return true if the player may join, false if the player does not satisfy the name restrictions
*/
public boolean fulfillsNameRestrictions(Player player) {
Collection<String> restrictions = restrictedNames.get(player.getName().toLowerCase());
if (Utils.isCollectionEmpty(restrictions)) {
return true;
}
String ip = PlayerUtils.getPlayerIp(player);
String domain = player.getAddress().getHostName();
return restrictions.stream()
.anyMatch(restriction -> ip.equals(restriction) || domain.equalsIgnoreCase(restriction));
}
/** /**
* Verifies whether the given value is allowed according to the given whitelist and blacklist settings. * Verifies whether the given value is allowed according to the given whitelist and blacklist settings.
* Whitelist has precedence over blacklist: if a whitelist is set, the value is rejected if not present * Whitelist has precedence over blacklist: if a whitelist is set, the value is rejected if not present
@ -144,11 +173,11 @@ public class ValidationService implements Reloadable {
private boolean validateWhitelistAndBlacklist(String value, Property<List<String>> whitelist, private boolean validateWhitelistAndBlacklist(String value, Property<List<String>> whitelist,
Property<List<String>> blacklist) { Property<List<String>> blacklist) {
List<String> whitelistValue = settings.getProperty(whitelist); List<String> whitelistValue = settings.getProperty(whitelist);
if (!CollectionUtils.isEmpty(whitelistValue)) { if (!Utils.isCollectionEmpty(whitelistValue)) {
return containsIgnoreCase(whitelistValue, value); return containsIgnoreCase(whitelistValue, value);
} }
List<String> blacklistValue = settings.getProperty(blacklist); List<String> blacklistValue = settings.getProperty(blacklist);
return CollectionUtils.isEmpty(blacklistValue) || !containsIgnoreCase(blacklistValue, value); return Utils.isCollectionEmpty(blacklistValue) || !containsIgnoreCase(blacklistValue, value);
} }
private static boolean containsIgnoreCase(Collection<String> coll, String needle) { private static boolean containsIgnoreCase(Collection<String> coll, String needle) {
@ -160,6 +189,26 @@ public class ValidationService implements Reloadable {
return false; return false;
} }
/**
* Loads the configured name restrictions into a Multimap by player name (all-lowercase).
*
* @param configuredRestrictions the restriction rules to convert to a map
* @return map of allowed IPs/domain names by player name
*/
private Multimap<String, String> loadNameRestrictions(List<String> configuredRestrictions) {
Multimap<String, String> restrictions = HashMultimap.create();
for (String restriction : configuredRestrictions) {
if (isInsideString(';', restriction)) {
String[] data = restriction.split(";");
restrictions.put(data[0].toLowerCase(), data[1]);
} else {
ConsoleLogger.warning("Restricted user rule must have a ';' separating name from restriction,"
+ " but found: '" + restriction + "'");
}
}
return restrictions;
}
public static final class ValidationResult { public static final class ValidationResult {
private final MessageKey messageKey; private final MessageKey messageKey;
private final String[] args; private final String[] args;
@ -195,6 +244,7 @@ public class ValidationService implements Reloadable {
public MessageKey getMessageKey() { public MessageKey getMessageKey() {
return messageKey; return messageKey;
} }
public String[] getArgs() { public String[] getArgs() {
return args; return args;
} }

View File

@ -19,7 +19,6 @@ import static fr.xephi.authme.util.FileUtils.copyFileFromResource;
public class Settings extends SettingsManager { public class Settings extends SettingsManager {
private final File pluginFolder; private final File pluginFolder;
private String[] welcomeMessage;
private String passwordEmailMessage; private String passwordEmailMessage;
private String recoveryCodeEmailMessage; private String recoveryCodeEmailMessage;
@ -56,19 +55,9 @@ public class Settings extends SettingsManager {
return recoveryCodeEmailMessage; return recoveryCodeEmailMessage;
} }
/**
* Return the lines to output after an in-game registration.
*
* @return The welcome message
*/
public String[] getWelcomeMessage() {
return welcomeMessage;
}
private void loadSettingsFromFiles() { private void loadSettingsFromFiles() {
passwordEmailMessage = readFile("email.html"); passwordEmailMessage = readFile("email.html");
recoveryCodeEmailMessage = readFile("recovery_code_email.html"); recoveryCodeEmailMessage = readFile("recovery_code_email.html");
welcomeMessage = readFile("welcome.txt").split("\\n");
} }
@Override @Override

View File

@ -71,6 +71,7 @@ public class SettingsMigrationService extends PlainMigrationService {
| hasOldHelpHeaderProperty(resource) | hasOldHelpHeaderProperty(resource)
| hasSupportOldPasswordProperty(resource) | hasSupportOldPasswordProperty(resource)
| convertToRegistrationType(resource) | convertToRegistrationType(resource)
| mergeAndMovePermissionGroupSettings(resource)
|| hasDeprecatedProperties(resource); || hasDeprecatedProperties(resource);
} }
@ -81,7 +82,8 @@ public class SettingsMigrationService extends PlainMigrationService {
"VeryGames", "settings.restrictions.allowAllCommandsIfRegistrationIsOptional", "DataSource.mySQLWebsite", "VeryGames", "settings.restrictions.allowAllCommandsIfRegistrationIsOptional", "DataSource.mySQLWebsite",
"Hooks.customAttributes", "Security.stop.kickPlayersBeforeStopping", "Hooks.customAttributes", "Security.stop.kickPlayersBeforeStopping",
"settings.restrictions.keepCollisionsDisabled", "settings.forceCommands", "settings.forceCommandsAsConsole", "settings.restrictions.keepCollisionsDisabled", "settings.forceCommands", "settings.forceCommandsAsConsole",
"settings.forceRegisterCommands", "settings.forceRegisterCommandsAsConsole"}; "settings.forceRegisterCommands", "settings.forceRegisterCommandsAsConsole",
"settings.sessions.sessionExpireOnIpChange"};
for (String deprecatedPath : deprecatedProperties) { for (String deprecatedPath : deprecatedProperties) {
if (resource.contains(deprecatedPath)) { if (resource.contains(deprecatedPath)) {
return true; return true;
@ -251,6 +253,26 @@ public class SettingsMigrationService extends PlainMigrationService {
return true; return true;
} }
private static boolean mergeAndMovePermissionGroupSettings(PropertyResource resource) {
boolean performedChanges;
// We have two old settings replaced by only one: move the first non-empty one
Property<String> oldUnloggedInGroup = newProperty("settings.security.unLoggedinGroup", "");
Property<String> oldRegisteredGroup = newProperty("GroupOptions.RegisteredPlayerGroup", "");
if (!oldUnloggedInGroup.getValue(resource).isEmpty()) {
performedChanges = moveProperty(oldUnloggedInGroup, PluginSettings.REGISTERED_GROUP, resource);
} else {
performedChanges = moveProperty(oldRegisteredGroup, PluginSettings.REGISTERED_GROUP, resource);
}
// Move paths of other old options
performedChanges |= moveProperty(newProperty("GroupOptions.UnregisteredPlayerGroup", ""),
PluginSettings.UNREGISTERED_GROUP, resource);
performedChanges |= moveProperty(newProperty("permission.EnablePermissionCheck", false),
PluginSettings.ENABLE_PERMISSION_CHECK, resource);
return performedChanges;
}
/** /**
* Checks for an old property path and moves it to a new path if present. * Checks for an old property path and moves it to a new path if present.
* *
@ -264,9 +286,10 @@ public class SettingsMigrationService extends PlainMigrationService {
Property<T> newProperty, Property<T> newProperty,
PropertyResource resource) { PropertyResource resource) {
if (resource.contains(oldProperty.getPath())) { if (resource.contains(oldProperty.getPath())) {
if (resource.contains(newProperty.getPath())) {
ConsoleLogger.info("Detected deprecated property " + oldProperty.getPath()); ConsoleLogger.info("Detected deprecated property " + oldProperty.getPath());
if (!resource.contains(newProperty.getPath())) { } else {
ConsoleLogger.info("Renamed " + oldProperty.getPath() + " to " + newProperty.getPath()); ConsoleLogger.info("Renaming " + oldProperty.getPath() + " to " + newProperty.getPath());
resource.setValue(newProperty.getPath(), oldProperty.getValue(resource)); resource.setValue(newProperty.getPath(), oldProperty.getValue(resource));
} }
return true; return true;

View File

@ -1,10 +1,9 @@
package fr.xephi.authme.settings; package fr.xephi.authme.settings;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.service.PluginHookService;
import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.service.PluginHookService;
import fr.xephi.authme.settings.properties.HooksSettings; import fr.xephi.authme.settings.properties.HooksSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings; import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.util.FileUtils; import fr.xephi.authme.util.FileUtils;
@ -43,11 +42,9 @@ public class SpawnLoader implements Reloadable {
* @param pluginFolder The AuthMe data folder * @param pluginFolder The AuthMe data folder
* @param settings The setting instance * @param settings The setting instance
* @param pluginHookService The plugin hooks instance * @param pluginHookService The plugin hooks instance
* @param dataSource The plugin auth database instance
*/ */
@Inject @Inject
SpawnLoader(@DataFolder File pluginFolder, Settings settings, PluginHookService pluginHookService, SpawnLoader(@DataFolder File pluginFolder, Settings settings, PluginHookService pluginHookService) {
DataSource dataSource) {
// TODO ljacqu 20160312: Check if resource could be copied and handle the case if not // TODO ljacqu 20160312: Check if resource could be copied and handle the case if not
File spawnFile = new File(pluginFolder, "spawn.yml"); File spawnFile = new File(pluginFolder, "spawn.yml");
FileUtils.copyFileFromResource(spawnFile, "spawn.yml"); FileUtils.copyFileFromResource(spawnFile, "spawn.yml");

View File

@ -0,0 +1,97 @@
package fr.xephi.authme.settings;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.GeoIpService;
import fr.xephi.authme.util.PlayerUtils;
import fr.xephi.authme.util.lazytags.Tag;
import fr.xephi.authme.util.lazytags.TagReplacer;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static fr.xephi.authme.util.FileUtils.copyFileFromResource;
import static fr.xephi.authme.util.lazytags.TagBuilder.createTag;
/**
* Configuration for the welcome message (welcome.txt).
*/
public class WelcomeMessageConfiguration implements Reloadable {
@DataFolder
@Inject
private File pluginFolder;
@Inject
private Server server;
@Inject
private GeoIpService geoIpService;
@Inject
private BukkitService bukkitService;
@Inject
private PlayerCache playerCache;
/** List of all supported tags for the welcome message. */
private final List<Tag<Player>> availableTags = Arrays.asList(
createTag("&", () -> "\u00a7"),
createTag("{PLAYER}", pl -> pl.getName()),
createTag("{ONLINE}", () -> Integer.toString(bukkitService.getOnlinePlayers().size())),
createTag("{MAXPLAYERS}", () -> Integer.toString(server.getMaxPlayers())),
createTag("{IP}", pl -> PlayerUtils.getPlayerIp(pl)),
createTag("{LOGINS}", () -> Integer.toString(playerCache.getLogged())),
createTag("{WORLD}", pl -> pl.getWorld().getName()),
createTag("{SERVER}", () -> server.getServerName()),
createTag("{VERSION}", () -> server.getBukkitVersion()),
createTag("{COUNTRY}", pl -> geoIpService.getCountryName(PlayerUtils.getPlayerIp(pl))));
private TagReplacer<Player> messageSupplier;
@PostConstruct
@Override
public void reload() {
List<String> welcomeMessage = readWelcomeFile();
messageSupplier = TagReplacer.newReplacer(availableTags, welcomeMessage);
}
/**
* Returns the welcome message for the given player.
*
* @param player the player for whom the welcome message should be prepared
* @return the welcome message
*/
public List<String> getWelcomeMessage(Player player) {
return messageSupplier.getAdaptedMessages(player);
}
/**
* @return the lines of the welcome message file
*/
private List<String> readWelcomeFile() {
File welcomeFile = new File(pluginFolder, "welcome.txt");
if (copyFileFromResource(welcomeFile, "welcome.txt")) {
try {
return Files.readAllLines(welcomeFile.toPath(), StandardCharsets.UTF_8);
} catch (IOException e) {
ConsoleLogger.logException("Failed to read welcome.txt file:", e);
}
} else {
ConsoleLogger.warning("Failed to copy welcome.txt from JAR");
}
return Collections.emptyList();
}
}

View File

@ -5,13 +5,21 @@ import ch.jalu.configme.resource.YamlFileResource;
import fr.xephi.authme.initialization.DataFolder; import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.initialization.Reloadable; import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.GeoIpService;
import fr.xephi.authme.util.FileUtils; import fr.xephi.authme.util.FileUtils;
import fr.xephi.authme.util.PlayerUtils;
import fr.xephi.authme.util.lazytags.Tag;
import fr.xephi.authme.util.lazytags.WrappedTagReplacer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File; import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Map; import java.util.Map;
import static fr.xephi.authme.util.lazytags.TagBuilder.createTag;
/** /**
* Manages configurable commands to be run when various events occur. * Manages configurable commands to be run when various events occur.
*/ */
@ -19,15 +27,20 @@ public class CommandManager implements Reloadable {
private final File dataFolder; private final File dataFolder;
private final BukkitService bukkitService; private final BukkitService bukkitService;
private final GeoIpService geoIpService;
private final CommandMigrationService commandMigrationService; private final CommandMigrationService commandMigrationService;
private final List<Tag<Player>> availableTags = buildAvailableTags();
private CommandConfig commandConfig; private WrappedTagReplacer<Command, Player> onJoinCommands;
private WrappedTagReplacer<Command, Player> onLoginCommands;
private WrappedTagReplacer<Command, Player> onRegisterCommands;
@Inject @Inject
CommandManager(@DataFolder File dataFolder, BukkitService bukkitService, CommandManager(@DataFolder File dataFolder, BukkitService bukkitService, GeoIpService geoIpService,
CommandMigrationService commandMigrationService) { CommandMigrationService commandMigrationService) {
this.dataFolder = dataFolder; this.dataFolder = dataFolder;
this.bukkitService = bukkitService; this.bukkitService = bukkitService;
this.geoIpService = geoIpService;
this.commandMigrationService = commandMigrationService; this.commandMigrationService = commandMigrationService;
reload(); reload();
} }
@ -38,7 +51,7 @@ public class CommandManager implements Reloadable {
* @param player the joining player * @param player the joining player
*/ */
public void runCommandsOnJoin(Player player) { public void runCommandsOnJoin(Player player) {
executeCommands(player, commandConfig.getOnJoin()); executeCommands(player, onJoinCommands.getAdaptedItems(player));
} }
/** /**
@ -47,7 +60,7 @@ public class CommandManager implements Reloadable {
* @param player the player who has registered * @param player the player who has registered
*/ */
public void runCommandsOnRegister(Player player) { public void runCommandsOnRegister(Player player) {
executeCommands(player, commandConfig.getOnRegister()); executeCommands(player, onRegisterCommands.getAdaptedItems(player));
} }
/** /**
@ -56,12 +69,12 @@ public class CommandManager implements Reloadable {
* @param player the player that logged in * @param player the player that logged in
*/ */
public void runCommandsOnLogin(Player player) { public void runCommandsOnLogin(Player player) {
executeCommands(player, commandConfig.getOnLogin()); executeCommands(player, onLoginCommands.getAdaptedItems(player));
} }
private void executeCommands(Player player, Map<String, Command> commands) { private void executeCommands(Player player, List<Command> commands) {
for (Command command : commands.values()) { for (Command command : commands) {
final String execution = command.getCommand().replace("%p", player.getName()); final String execution = command.getCommand();
if (Executor.CONSOLE.equals(command.getExecutor())) { if (Executor.CONSOLE.equals(command.getExecutor())) {
bukkitService.dispatchConsoleCommand(execution); bukkitService.dispatchConsoleCommand(execution);
} else { } else {
@ -77,8 +90,22 @@ public class CommandManager implements Reloadable {
SettingsManager settingsManager = new SettingsManager( SettingsManager settingsManager = new SettingsManager(
new YamlFileResource(file), commandMigrationService, CommandSettingsHolder.class); new YamlFileResource(file), commandMigrationService, CommandSettingsHolder.class);
commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS); CommandConfig commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS);
onJoinCommands = newReplacer(commandConfig.getOnJoin());
onLoginCommands = newReplacer(commandConfig.getOnLogin());
onRegisterCommands = newReplacer(commandConfig.getOnRegister());
} }
private WrappedTagReplacer<Command, Player> newReplacer(Map<String, Command> commands) {
return new WrappedTagReplacer<>(availableTags, commands.values(), Command::getCommand,
(cmd, text) -> new Command(text, cmd.getExecutor()));
}
private List<Tag<Player>> buildAvailableTags() {
return Arrays.asList(
createTag("%p", pl -> pl.getName()),
createTag("%nick", pl -> pl.getDisplayName()),
createTag("%ip", pl -> PlayerUtils.getPlayerIp(pl)),
createTag("%country", pl -> geoIpService.getCountryName(PlayerUtils.getPlayerIp(pl))));
}
} }

View File

@ -24,7 +24,12 @@ public final class CommandSettingsHolder implements SettingsHolder {
public static Map<String, String[]> sectionComments() { public static Map<String, String[]> sectionComments() {
String[] comments = { String[] comments = {
"This configuration file allows you to execute commands on various events.", "This configuration file allows you to execute commands on various events.",
"%p in commands will be replaced with the player name.", "Supported placeholders in commands:",
" %p is replaced with the player name.",
" %nick is replaced with the player's nick name",
" %ip is replaced with the player's IP address",
" %country is replaced with the player's country",
"",
"For example, if you want to send a welcome message to a player who just registered:", "For example, if you want to send a welcome message to a player who just registered:",
"onRegister:", "onRegister:",
" welcome:", " welcome:",

View File

@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public class BackupSettings implements SettingsHolder { public final class BackupSettings implements SettingsHolder {
@Comment("Enable or disable automatic backup") @Comment("Enable or disable automatic backup")
public static final Property<Boolean> ENABLED = public static final Property<Boolean> ENABLED =

View File

@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public class ConverterSettings implements SettingsHolder { public final class ConverterSettings implements SettingsHolder {
@Comment("Rakamak file name") @Comment("Rakamak file name")
public static final Property<String> RAKAMAK_FILE_NAME = public static final Property<String> RAKAMAK_FILE_NAME =

View File

@ -7,10 +7,10 @@ import fr.xephi.authme.datasource.DataSourceType;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public class DatabaseSettings implements SettingsHolder { public final class DatabaseSettings implements SettingsHolder {
@Comment({"What type of database do you want to use?", @Comment({"What type of database do you want to use?",
"Valid values: sqlite, mysql"}) "Valid values: SQLITE, MYSQL"})
public static final Property<DataSourceType> BACKEND = public static final Property<DataSourceType> BACKEND =
newProperty(DataSourceType.class, "DataSource.backend", DataSourceType.SQLITE); newProperty(DataSourceType.class, "DataSource.backend", DataSourceType.SQLITE);
@ -26,11 +26,15 @@ public class DatabaseSettings implements SettingsHolder {
public static final Property<String> MYSQL_PORT = public static final Property<String> MYSQL_PORT =
newProperty("DataSource.mySQLPort", "3306"); newProperty("DataSource.mySQLPort", "3306");
@Comment("Username about Database Connection Infos") @Comment("Connect to MySQL database over SSL")
public static final Property<Boolean> MYSQL_USE_SSL =
newProperty("DataSource.mySQLUseSSL", true);
@Comment("Username to connect to the MySQL database")
public static final Property<String> MYSQL_USERNAME = public static final Property<String> MYSQL_USERNAME =
newProperty("DataSource.mySQLUsername", "authme"); newProperty("DataSource.mySQLUsername", "authme");
@Comment("Password about Database Connection Infos") @Comment("Password to connect to the MySQL database")
public static final Property<String> MYSQL_PASSWORD = public static final Property<String> MYSQL_PASSWORD =
newProperty("DataSource.mySQLPassword", "12345"); newProperty("DataSource.mySQLPassword", "12345");
@ -58,10 +62,6 @@ public class DatabaseSettings implements SettingsHolder {
public static final Property<String> MYSQL_COL_PASSWORD = public static final Property<String> MYSQL_COL_PASSWORD =
newProperty("DataSource.mySQLColumnPassword", "password"); newProperty("DataSource.mySQLColumnPassword", "password");
@Comment("Request mysql over SSL")
public static final Property<Boolean> MYSQL_USE_SSL =
newProperty("DataSource.mySQLUseSSL", true);
@Comment("Column for storing players passwords salts") @Comment("Column for storing players passwords salts")
public static final Property<String> MYSQL_COL_SALT = public static final Property<String> MYSQL_COL_SALT =
newProperty("ExternalBoardOptions.mySQLColumnSalt", ""); newProperty("ExternalBoardOptions.mySQLColumnSalt", "");

View File

@ -9,7 +9,7 @@ import java.util.List;
import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public class EmailSettings implements SettingsHolder { public final class EmailSettings implements SettingsHolder {
@Comment("Email SMTP server host") @Comment("Email SMTP server host")
public static final Property<String> SMTP_HOST = public static final Property<String> SMTP_HOST =
@ -19,6 +19,10 @@ public class EmailSettings implements SettingsHolder {
public static final Property<Integer> SMTP_PORT = public static final Property<Integer> SMTP_PORT =
newProperty("Email.mailPort", 465); newProperty("Email.mailPort", 465);
@Comment("Only affects port 25: enable TLS/STARTTLS?")
public static final Property<Boolean> PORT25_USE_TLS =
newProperty("Email.useTls", true);
@Comment("Email account which sends the mails") @Comment("Email account which sends the mails")
public static final Property<String> MAIL_ACCOUNT = public static final Property<String> MAIL_ACCOUNT =
newProperty("Email.mailAccount", ""); newProperty("Email.mailAccount", "");

View File

@ -9,7 +9,7 @@ import java.util.List;
import static ch.jalu.configme.properties.PropertyInitializer.newListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public class HooksSettings implements SettingsHolder { public final class HooksSettings implements SettingsHolder {
@Comment("Do we need to hook with multiverse for spawn checking?") @Comment("Do we need to hook with multiverse for spawn checking?")
public static final Property<Boolean> MULTIVERSE = public static final Property<Boolean> MULTIVERSE =
@ -54,17 +54,22 @@ public class HooksSettings implements SettingsHolder {
public static final Property<Integer> PHPBB_ACTIVATED_GROUP_ID = public static final Property<Integer> PHPBB_ACTIVATED_GROUP_ID =
newProperty("ExternalBoardOptions.phpbbActivatedGroupId", 2); newProperty("ExternalBoardOptions.phpbbActivatedGroupId", 2);
@Comment("IP Board table prefix defined during the IP Board installation process")
public static final Property<String> IPB_TABLE_PREFIX =
newProperty("ExternalBoardOptions.IPBTablePrefix", "ipb_");
@Comment("IP Board default group ID; 3 is the default registered group defined by IP Board")
public static final Property<Integer> IPB_ACTIVATED_GROUP_ID =
newProperty("ExternalBoardOptions.IPBActivatedGroupId", 3);
@Comment("XenForo default group ID; 2 is the default registered group defined by Xenforo")
public static final Property<Integer> XF_ACTIVATED_GROUP_ID =
newProperty("ExternalBoardOptions.XFActivatedGroupId", 2);
@Comment("Wordpress prefix defined during WordPress installation") @Comment("Wordpress prefix defined during WordPress installation")
public static final Property<String> WORDPRESS_TABLE_PREFIX = public static final Property<String> WORDPRESS_TABLE_PREFIX =
newProperty("ExternalBoardOptions.wordpressTablePrefix", "wp_"); newProperty("ExternalBoardOptions.wordpressTablePrefix", "wp_");
@Comment("Unregistered permission group")
public static final Property<String> UNREGISTERED_GROUP =
newProperty("GroupOptions.UnregisteredPlayerGroup", "");
@Comment("Registered permission group")
public static final Property<String> REGISTERED_GROUP =
newProperty("GroupOptions.RegisteredPlayerGroup", "");
private HooksSettings() { private HooksSettings() {
} }

View File

@ -7,7 +7,7 @@ import fr.xephi.authme.output.LogLevel;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public class PluginSettings implements SettingsHolder { public final class PluginSettings implements SettingsHolder {
@Comment({ @Comment({
"Do you want to enable the session feature?", "Do you want to enable the session feature?",
@ -22,20 +22,11 @@ public class PluginSettings implements SettingsHolder {
@Comment({ @Comment({
"After how many minutes should a session expire?", "After how many minutes should a session expire?",
"Remember that sessions will end only after the timeout, and", "A player's session ends after the timeout or if his IP has changed"
"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"
}) })
public static final Property<Integer> SESSIONS_TIMEOUT = public static final Property<Integer> SESSIONS_TIMEOUT =
newProperty("settings.sessions.timeout", 10); newProperty("settings.sessions.timeout", 10);
@Comment({
"Should the session expire if the player tries to log in with",
"another IP address?"
})
public static final Property<Boolean> SESSIONS_EXPIRE_ON_IP_CHANGE =
newProperty("settings.sessions.sessionExpireOnIpChange", true);
@Comment({ @Comment({
"Message language, available languages:", "Message language, available languages:",
"https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md" "https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md"
@ -44,13 +35,33 @@ public class PluginSettings implements SettingsHolder {
newProperty("settings.messagesLanguage", "en"); newProperty("settings.messagesLanguage", "en");
@Comment({ @Comment({
"Take care with this option; if you want", "Enables switching a player to defined permission groups before they log in.",
"to use group switching of AuthMe", "See below for a detailed explanation."
"for unloggedIn players, set this setting to true.",
"Default is false."
}) })
public static final Property<Boolean> ENABLE_PERMISSION_CHECK = public static final Property<Boolean> ENABLE_PERMISSION_CHECK =
newProperty("permission.EnablePermissionCheck", false); newProperty("GroupOptions.enablePermissionCheck", false);
@Comment({
"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'"
})
public static final Property<String> REGISTERED_GROUP =
newProperty("GroupOptions.registeredPlayerGroup", "");
@Comment({
"Similar to above, unregistered players can be set to the following",
"permissions group"
})
public static final Property<String> UNREGISTERED_GROUP =
newProperty("GroupOptions.unregisteredPlayerGroup", "");
@Comment({ @Comment({
"Log level: INFO, FINE, DEBUG. Use INFO for general messages,", "Log level: INFO, FINE, DEBUG. Use INFO for general messages,",

View File

@ -10,7 +10,7 @@ import static ch.jalu.configme.properties.PropertyInitializer.newListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public class ProtectionSettings implements SettingsHolder { public final class ProtectionSettings implements SettingsHolder {
@Comment("Enable some servers protection (country based login, antibot)") @Comment("Enable some servers protection (country based login, antibot)")
public static final Property<Boolean> ENABLE_PROTECTION = public static final Property<Boolean> ENABLE_PROTECTION =

View File

@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public class PurgeSettings implements SettingsHolder { public final class PurgeSettings implements SettingsHolder {
@Comment("If enabled, AuthMe automatically purges old, unused accounts") @Comment("If enabled, AuthMe automatically purges old, unused accounts")
public static final Property<Boolean> USE_AUTO_PURGE = public static final Property<Boolean> USE_AUTO_PURGE =

View File

@ -8,7 +8,7 @@ import fr.xephi.authme.process.register.RegistrationType;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public class RegistrationSettings implements SettingsHolder { public final class RegistrationSettings implements SettingsHolder {
@Comment("Enable registration on the server?") @Comment("Enable registration on the server?")
public static final Property<Boolean> IS_ENABLED = public static final Property<Boolean> IS_ENABLED =
@ -42,7 +42,8 @@ public class RegistrationSettings implements SettingsHolder {
"EMAIL_MANDATORY = for password register: 2nd argument MUST be an email address" "EMAIL_MANDATORY = for password register: 2nd argument MUST be an email address"
}) })
public static final Property<RegisterSecondaryArgument> REGISTER_SECOND_ARGUMENT = public static final Property<RegisterSecondaryArgument> REGISTER_SECOND_ARGUMENT =
newProperty(RegisterSecondaryArgument.class, "settings.registration.secondArg", RegisterSecondaryArgument.CONFIRMATION); newProperty(RegisterSecondaryArgument.class, "settings.registration.secondArg",
RegisterSecondaryArgument.CONFIRMATION);
@Comment({ @Comment({
"Do we force kick a player after a successful registration?", "Do we force kick a player after a successful registration?",

View File

@ -10,7 +10,7 @@ import static ch.jalu.configme.properties.PropertyInitializer.newListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public class RestrictionSettings implements SettingsHolder { public final class RestrictionSettings implements SettingsHolder {
@Comment({ @Comment({
"Can not authenticated players chat?", "Can not authenticated players chat?",
@ -81,9 +81,13 @@ public class RestrictionSettings implements SettingsHolder {
"Example:", "Example:",
" AllowedRestrictedUser:", " AllowedRestrictedUser:",
" - playername;127.0.0.1"}) " - playername;127.0.0.1"})
public static final Property<List<String>> ALLOWED_RESTRICTED_USERS = public static final Property<List<String>> RESTRICTED_USERS =
newLowercaseListProperty("settings.restrictions.AllowedRestrictedUser"); newLowercaseListProperty("settings.restrictions.AllowedRestrictedUser");
@Comment("Ban unknown IPs trying to log in with a restricted username?")
public static final Property<Boolean> BAN_UNKNOWN_IP =
newProperty("settings.restrictions.banUnsafedIP", false);
@Comment("Should unregistered players be kicked immediately?") @Comment("Should unregistered players be kicked immediately?")
public static final Property<Boolean> KICK_NON_REGISTERED = public static final Property<Boolean> KICK_NON_REGISTERED =
newProperty("settings.restrictions.kickNonRegistered", false); newProperty("settings.restrictions.kickNonRegistered", false);
@ -115,7 +119,7 @@ public class RestrictionSettings implements SettingsHolder {
public static final Property<Integer> TIMEOUT = public static final Property<Integer> TIMEOUT =
newProperty("settings.restrictions.timeout", 30); newProperty("settings.restrictions.timeout", 30);
@Comment("Regex syntax of allowed characters in the player name.") @Comment("Regex pattern of allowed characters in the player name.")
public static final Property<String> ALLOWED_NICKNAME_CHARACTERS = public static final Property<String> ALLOWED_NICKNAME_CHARACTERS =
newProperty("settings.restrictions.allowedNicknameCharacters", "[a-zA-Z0-9_]*"); newProperty("settings.restrictions.allowedNicknameCharacters", "[a-zA-Z0-9_]*");
@ -140,10 +144,6 @@ public class RestrictionSettings implements SettingsHolder {
public static final Property<Boolean> DISPLAY_OTHER_ACCOUNTS = public static final Property<Boolean> DISPLAY_OTHER_ACCOUNTS =
newProperty("settings.restrictions.displayOtherAccounts", true); newProperty("settings.restrictions.displayOtherAccounts", true);
@Comment("Ban ip when the ip is not the ip registered in database")
public static final Property<Boolean> BAN_UNKNOWN_IP =
newProperty("settings.restrictions.banUnsafedIP", false);
@Comment("Spawn priority; values: authme, essentials, multiverse, default") @Comment("Spawn priority; values: authme, essentials, multiverse, default")
public static final Property<String> SPAWN_PRIORITY = public static final Property<String> SPAWN_PRIORITY =
newProperty("settings.restrictions.spawnPriority", "authme,essentials,multiverse,default"); newProperty("settings.restrictions.spawnPriority", "authme,essentials,multiverse,default");

View File

@ -12,7 +12,7 @@ import java.util.Set;
import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseListProperty; import static ch.jalu.configme.properties.PropertyInitializer.newLowercaseListProperty;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty; import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public class SecuritySettings implements SettingsHolder { public final class SecuritySettings implements SettingsHolder {
@Comment({"Stop the server if we can't contact the sql database", @Comment({"Stop the server if we can't contact the sql database",
"Take care with this, if you set this to false,", "Take care with this, if you set this to false,",
@ -40,6 +40,10 @@ public class SecuritySettings implements SettingsHolder {
public static final Property<Integer> CAPTCHA_LENGTH = public static final Property<Integer> CAPTCHA_LENGTH =
newProperty("Security.captcha.captchaLength", 5); newProperty("Security.captcha.captchaLength", 5);
@Comment("Minutes after which login attempts count is reset for a player")
public static final Property<Integer> CAPTCHA_COUNT_MINUTES_BEFORE_RESET =
newProperty("Security.captcha.captchaCountReset", 60);
@Comment("Minimum length of password") @Comment("Minimum length of password")
public static final Property<Integer> MIN_PASSWORD_LENGTH = public static final Property<Integer> MIN_PASSWORD_LENGTH =
newProperty("settings.security.minPasswordLength", 5); newProperty("settings.security.minPasswordLength", 5);
@ -48,22 +52,6 @@ public class SecuritySettings implements SettingsHolder {
public static final Property<Integer> MAX_PASSWORD_LENGTH = public static final Property<Integer> MAX_PASSWORD_LENGTH =
newProperty("settings.security.passwordMaxLength", 30); newProperty("settings.security.passwordMaxLength", 30);
@Comment({
"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"
})
public static final Property<String> UNLOGGEDIN_GROUP =
newProperty("settings.security.unLoggedinGroup", "unLoggedinGroup");
@Comment({ @Comment({
"Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL,", "Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL,",
"MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB,", "MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB,",
@ -98,7 +86,8 @@ public class SecuritySettings implements SettingsHolder {
"- 'password'", "- 'password'",
"- 'help'"}) "- 'help'"})
public static final Property<List<String>> UNSAFE_PASSWORDS = public static final Property<List<String>> UNSAFE_PASSWORDS =
newLowercaseListProperty("settings.security.unsafePasswords", "123456", "password", "qwerty", "12345", "54321", "123456789", "help"); newLowercaseListProperty("settings.security.unsafePasswords",
"123456", "password", "qwerty", "12345", "54321", "123456789", "help");
@Comment("Tempban a user's IP address if they enter the wrong password too many times") @Comment("Tempban a user's IP address if they enter the wrong password too many times")
public static final Property<Boolean> TEMPBAN_ON_MAX_LOGINS = public static final Property<Boolean> TEMPBAN_ON_MAX_LOGINS =
@ -126,6 +115,13 @@ public class SecuritySettings implements SettingsHolder {
public static final Property<Integer> RECOVERY_CODE_HOURS_VALID = public static final Property<Integer> RECOVERY_CODE_HOURS_VALID =
newProperty("Security.recoveryCode.validForHours", 4); newProperty("Security.recoveryCode.validForHours", 4);
@Comment({
"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."
})
public static final Property<Integer> EMAIL_RECOVERY_COOLDOWN_SECONDS =
newProperty("Security.emailRecovery.cooldown", 60);
private SecuritySettings() { private SecuritySettings() {
} }

View File

@ -3,20 +3,19 @@ package fr.xephi.authme.task.purge;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.PurgeSettings; import fr.xephi.authme.settings.properties.PurgeSettings;
import fr.xephi.authme.service.BukkitService; import fr.xephi.authme.util.Utils;
import fr.xephi.authme.util.CollectionUtils;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
// TODO: move into services. -sgdc3 import static fr.xephi.authme.util.Utils.logAndSendMessage;
/** /**
* Initiates purge tasks. * Initiates purge tasks.
@ -75,7 +74,7 @@ public class PurgeService {
public void runPurge(CommandSender sender, long until, boolean includeEntriesWithLastLoginZero) { public void runPurge(CommandSender sender, long until, boolean includeEntriesWithLastLoginZero) {
//todo: note this should may run async because it may executes a SQL-Query //todo: note this should may run async because it may executes a SQL-Query
Set<String> toPurge = dataSource.getRecordsToPurge(until, includeEntriesWithLastLoginZero); Set<String> toPurge = dataSource.getRecordsToPurge(until, includeEntriesWithLastLoginZero);
if (CollectionUtils.isEmpty(toPurge)) { if (Utils.isCollectionEmpty(toPurge)) {
logAndSendMessage(sender, "No players to purge"); logAndSendMessage(sender, "No players to purge");
return; return;
} }
@ -119,12 +118,4 @@ public class PurgeService {
void executePurge(Collection<OfflinePlayer> players, Collection<String> names) { void executePurge(Collection<OfflinePlayer> players, Collection<String> names) {
purgeExecutor.executePurge(players, names); purgeExecutor.executePurge(players, names);
} }
private static void logAndSendMessage(CommandSender sender, String message) {
ConsoleLogger.info(message);
// Make sure sender is not console user, which will see the message from ConsoleLogger already
if (sender != null && !(sender instanceof ConsoleCommandSender)) {
sender.sendMessage(message);
}
}
} }

View File

@ -1,63 +0,0 @@
package fr.xephi.authme.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Utils class for collections.
*/
public final class CollectionUtils {
// Utility class
private CollectionUtils() {
}
/**
* Get a range from a list based on start and count parameters in a safe way.
*
* @param <T> element
* @param list The List
* @param start The start index
* @param count The number of elements to add
*
* @return The sublist consisting at most of {@code count} elements (less if the parameters
* exceed the size of the list)
*/
public static <T> List<T> getRange(List<T> list, int start, int count) {
if (start >= list.size() || count <= 0) {
return new ArrayList<>();
} else if (start < 0) {
start = 0;
}
int end = Math.min(list.size(), start + count);
return list.subList(start, end);
}
/**
* Get all elements from a list starting from the given index.
*
* @param <T> element
* @param list The List
* @param start The start index
*
* @return The sublist of all elements from index {@code start} and on; empty list
* if the start index exceeds the list's size
*/
public static <T> List<T> getRange(List<T> list, int start) {
if (start >= list.size()) {
return new ArrayList<>();
}
return getRange(list, start, list.size() - start);
}
/**
* Null-safe way to check whether a collection is empty or not.
*
* @param coll The collection to verify
* @return True if the collection is null or empty, false otherwise
*/
public static boolean isEmpty(Collection<?> coll) {
return coll == null || coll.isEmpty();
}
}

View File

@ -6,7 +6,7 @@ import org.bukkit.entity.Player;
/** /**
* Player utilities. * Player utilities.
*/ */
public class PlayerUtils { public final class PlayerUtils {
// Utility class // Utility class
private PlayerUtils() { private PlayerUtils() {

View File

@ -8,7 +8,7 @@ import java.util.Random;
*/ */
public final class RandomStringUtils { public final class RandomStringUtils {
private static final String CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final char[] CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static final Random RANDOM = new SecureRandom(); private static final Random RANDOM = new SecureRandom();
private static final int HEX_MAX_INDEX = 16; private static final int HEX_MAX_INDEX = 16;
private static final int LOWER_ALPHANUMERIC_INDEX = 36; private static final int LOWER_ALPHANUMERIC_INDEX = 36;
@ -24,7 +24,7 @@ public final class RandomStringUtils {
* @return The random string * @return The random string
*/ */
public static String generate(int length) { public static String generate(int length) {
return generate(length, LOWER_ALPHANUMERIC_INDEX); return generateString(length, LOWER_ALPHANUMERIC_INDEX);
} }
/** /**
@ -35,7 +35,7 @@ public final class RandomStringUtils {
* @return The random hexadecimal string * @return The random hexadecimal string
*/ */
public static String generateHex(int length) { public static String generateHex(int length) {
return generate(length, HEX_MAX_INDEX); return generateString(length, HEX_MAX_INDEX);
} }
/** /**
@ -46,16 +46,16 @@ public final class RandomStringUtils {
* @return The random string * @return The random string
*/ */
public static String generateLowerUpper(int length) { public static String generateLowerUpper(int length) {
return generate(length, CHARS.length()); return generateString(length, CHARS.length);
} }
private static String generate(int length, int maxIndex) { private static String generateString(int length, int maxIndex) {
if (length < 0) { if (length < 0) {
throw new IllegalArgumentException("Length must be positive but was " + length); throw new IllegalArgumentException("Length must be positive but was " + length);
} }
StringBuilder sb = new StringBuilder(length); StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
sb.append(CHARS.charAt(RANDOM.nextInt(maxIndex))); sb.append(CHARS[RANDOM.nextInt(maxIndex)]);
} }
return sb.toString(); return sb.toString();
} }

View File

@ -42,7 +42,7 @@ public final class StringUtils {
* *
* @return True if the string contains at least one of the items * @return True if the string contains at least one of the items
*/ */
public static boolean containsAny(String str, String... pieces) { public static boolean containsAny(String str, Iterable<String> pieces) {
if (str == null) { if (str == null) {
return false; return false;
} }
@ -76,4 +76,20 @@ public final class StringUtils {
public static String formatException(Throwable th) { public static String formatException(Throwable th) {
return "[" + th.getClass().getSimpleName() + "]: " + th.getMessage(); return "[" + th.getClass().getSimpleName() + "]: " + th.getMessage();
} }
/**
* Check that the given needle is in the middle of the haystack, i.e. that the haystack
* contains the needle and that it is not at the very start or end.
*
* @param needle the needle to search for
* @param haystack the haystack to search in
*
* @return true if the needle is in the middle of the word, false otherwise
*/
// Note ljacqu 20170314: `needle` is restricted to char type intentionally because something like
// isInsideString("11", "2211") would unexpectedly return true...
public static boolean isInsideString(char needle, String haystack) {
int index = haystack.indexOf(needle);
return index > 0 && index < haystack.length() - 1;
}
} }

View File

@ -1,7 +1,10 @@
package fr.xephi.authme.util; package fr.xephi.authme.util;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import java.util.Collection;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
@ -50,6 +53,32 @@ public final class Utils {
} }
} }
/**
* Sends a message to the given sender (null safe), and logs the message to the console.
* This method is aware that the command sender might be the console sender and avoids
* displaying the message twice in this case.
*
* @param sender the sender to inform
* @param message the message to log and send
*/
public static void logAndSendMessage(CommandSender sender, String message) {
ConsoleLogger.info(message);
// Make sure sender is not console user, which will see the message from ConsoleLogger already
if (sender != null && !(sender instanceof ConsoleCommandSender)) {
sender.sendMessage(message);
}
}
/**
* Null-safe way to check whether a collection is empty or not.
*
* @param coll The collection to verify
* @return True if the collection is null or empty, false otherwise
*/
public static boolean isCollectionEmpty(Collection<?> coll) {
return coll == null || coll.isEmpty();
}
/** /**
* Return the available core count of the JVM. * Return the available core count of the JVM.
* *

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