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 ###
*.class
MANIFEST.MF
# Package Files
#*.jar

View File

@ -19,20 +19,14 @@
- Project status:
- Dependencies: [![Dependencies status](https://www.versioneye.com/user/projects/57b182e8d6ffcd0032d7cf2d/badge.svg)](https://www.versioneye.com/user/projects/57b182e8d6ffcd0032d7cf2d)
- Test coverage: [![Coverage status](https://coveralls.io/repos/AuthMe-Team/AuthMeReloaded/badge.svg?branch=master&service=github)](https://coveralls.io/github/AuthMe-Team/AuthMeReloaded?branch=master)
- Code climate: [![Code Climate](https://codeclimate.com/github/AuthMe/AuthMeReloaded/badges/gpa.svg)](https://codeclimate.com/github/AuthMe/AuthMeReloaded)
- Development resources:
- <a href="http://ci.xephi.fr/job/AuthMeReloaded/javadoc/">JavaDocs</a>
- <a href="http://ci.xephi.fr/plugin/repository/everything/">Maven Repository</a>
#####Statistics:
McStats: http://mcstats.org/plugin/AuthMe
<img src="http://i.mcstats.org/AuthMe/Global+Statistics.borderless.png">
<img src="http://i.mcstats.org/AuthMe/Rank.borderless.png">
<img src="http://i.mcstats.org/AuthMe/Version+Demographics.borderless.png">
- Statistics:
- bStats: [AuthMe on bstats.org](https://bstats.org/plugin/bukkit/AuthMe)
<hr>
@ -92,7 +86,7 @@ You can also create your own translation file and, if you want, you can share it
<li>DoubleSaltedMD5: SALTED2MD5</li>
<li>WordPress: WORDPRESS</li>
</ul></li>
<li>Custom MySQL tables/columns names (useful with forums databases)</li>
<li>Custom MySQL tables/columns names (useful with forum databases)</li>
<li><strong>Cached database queries!</strong></li>
<li><strong>Fully compatible with Citizens2, CombatTag, CombatTagPlus!</strong></li>
<li>Compatible with Minecraft mods like <strong>BuildCraft or RedstoneCraft</strong></li>

View File

@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Sat Jan 14 22:12:16 CET 2017. See docs/config/config.tpl.md -->
<!-- File auto-generated on Sat Feb 25 21:59:18 CET 2017. See docs/config/config.tpl.md -->
## AuthMe Configuration
The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder,
@ -18,9 +18,11 @@ DataSource:
mySQLHost: '127.0.0.1'
# Database port
mySQLPort: '3306'
# Username about Database Connection Infos
# Connect to MySQL database over SSL
mySQLUseSSL: true
# Username to connect to the MySQL database
mySQLUsername: 'authme'
# Password about Database Connection Infos
# Password to connect to the MySQL database
mySQLPassword: '12345'
# Database Name, use with converters or as SQLITE database name
mySQLDatabase: 'authme'
@ -34,8 +36,6 @@ DataSource:
mySQLRealName: 'realname'
# Column for storing players passwords
mySQLColumnPassword: 'password'
# Request mysql over SSL
mySQLUseSSL: true
# Column for storing players emails
mySQLColumnEmail: 'email'
# Column for storing if a player is logged in or not
@ -94,13 +94,8 @@ settings:
# expired, he will not need to authenticate.
enabled: false
# After how many minutes should a session expire?
# Remember that sessions will end only after the timeout, and
# if the player's IP has changed but the timeout hasn't expired,
# the player will be kicked from the server due to invalid session
# A player's session ends after the timeout or if his IP has changed
timeout: 10
# Should the session expire if the player tries to log in with
# another IP address?
sessionExpireOnIpChange: true
# Message language, available languages:
# https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md
messagesLanguage: 'en'
@ -161,6 +156,8 @@ settings:
# AllowedRestrictedUser:
# - playername;127.0.0.1
AllowedRestrictedUser: []
# Ban unknown IPs trying to log in with a restricted username?
banUnsafedIP: false
# Should unregistered players be kicked immediately?
kickNonRegistered: false
# Should players be kicked on wrong password?
@ -177,7 +174,7 @@ settings:
# After how many seconds should players who fail to login or register
# be kicked? Set to 0 to disable.
timeout: 30
# Regex syntax of allowed characters in the player name.
# Regex pattern of allowed characters in the player name.
allowedNicknameCharacters: '[a-zA-Z0-9_]*'
# How far can unregistered players walk?
# Set to 0 for unlimited radius
@ -189,8 +186,6 @@ settings:
# Should we display all other accounts from a player when he joins?
# permission: /authme.admin.accounts
displayOtherAccounts: true
# Ban ip when the ip is not the ip registered in database
banUnsafedIP: false
# Spawn priority; values: authme, essentials, multiverse, default
spawnPriority: 'authme,essentials,multiverse,default'
# Maximum Login authorized by IP
@ -223,18 +218,6 @@ settings:
minPasswordLength: 5
# Maximum length of password
passwordMaxLength: 30
# This is a very important option: every time a player joins the server,
# if they are registered, AuthMe will switch him to unLoggedInGroup.
# This should prevent all major exploits.
# You can set up your permission plugin with this special group to have no permissions,
# or only permission to chat (or permission to send private messages etc.).
# The better way is to set up this group with few permissions, so if a player
# tries to exploit an account they can do only what you've defined for the group.
# After, a logged in player will be moved to his correct permissions group!
# Please note that the group name is case-sensitive, so 'admin' is different from 'Admin'
# Otherwise your group will be wiped and the player will join in the default group []!
# Example unLoggedinGroup: NotLogged
unLoggedinGroup: 'unLoggedinGroup'
# Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL,
# MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB,
# PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (for developers only). See full list at
@ -317,12 +300,24 @@ settings:
# Do we need to prevent people to login with another case?
# If Xephi is registered, then Xephi can login, but not XEPHI/xephi/XePhI
preventOtherCase: true
permission:
# Take care with this option; if you want
# to use group switching of AuthMe
# for unloggedIn players, set this setting to true.
# Default is false.
EnablePermissionCheck: false
GroupOptions:
# Enables switching a player to defined permission groups before they log in.
# See below for a detailed explanation.
enablePermissionCheck: false
# This is a very important option: if a registered player joins the server
# AuthMe will switch him to unLoggedInGroup. This should prevent all major exploits.
# You can set up your permission plugin with this special group to have no permissions,
# or only permission to chat (or permission to send private messages etc.).
# The better way is to set up this group with few permissions, so if a player
# tries to exploit an account they can do only what you've defined for the group.
# After login, the player will be moved to his correct permissions group!
# Please note that the group name is case-sensitive, so 'admin' is different from 'Admin'
# Otherwise your group will be wiped and the player will join in the default group []!
# Example: registeredPlayerGroup: 'NotLogged'
registeredPlayerGroup: ''
# Similar to above, unregistered players can be set to the following
# permissions group
unregisteredPlayerGroup: ''
Email:
# Email SMTP server host
mailSMTP: 'smtp.gmail.com'
@ -366,18 +361,13 @@ Hooks:
disableSocialSpy: false
# Do we need to force /motd Essentials command on join?
useEssentialsMotd: false
GroupOptions:
# Unregistered permission group
UnregisteredPlayerGroup: ''
# Registered permission group
RegisteredPlayerGroup: ''
Protection:
# Enable some servers protection (country based login, antibot)
enableProtection: false
# Apply the protection also to registered usernames
enableProtectionRegistered: true
# Countries allowed to join the server and register. For country codes, see
# http://dev.bukkit.org/bukkit-plugins/authme-reloaded/pages/countries-codes/
# https://dev.bukkit.org/projects/authme-reloaded/pages/countries-codes
# PLEASE USE QUOTES!
countries:
- 'US'
@ -432,6 +422,8 @@ Security:
maxLoginTry: 5
# Captcha length
captchaLength: 5
# Minutes after which login attempts count is reset for a player
captchaCountReset: 60
tempban:
# Tempban a user's IP address if they enter the wrong password too many times
enableTempban: false
@ -448,6 +440,10 @@ Security:
length: 8
# How many hours is a recovery code valid for?
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:
# Enable or disable automatic backup
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 -->
<!-- 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
The following translations are available in AuthMe. Set `messagesLanguage` to the language code
@ -8,35 +8,34 @@ in your config.yml to use the language, or use another language code to start a
Code | Language | Translated | &nbsp;
---- | -------- | ---------: | ------
[en](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_en.yml) | English | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 69% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=69&h=5&txtpad=1" alt="bar" />
[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[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 | 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 | 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 | 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" />
[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 62% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=62&h=5&txtpad=1" alt="bar" />
[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 66% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=66&h=5&txtpad=1" alt="bar" />
[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 | 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" />
[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 70% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=70&h=5&txtpad=1" alt="bar" />
[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 99% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ee55&w=99&h=5&txtpad=1" alt="bar" />
[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 70% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=70&h=5&txtpad=1" alt="bar" />
[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 | 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 | 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" />
[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 72% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=72&h=5&txtpad=1" alt="bar" />
[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 53% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb6600&w=53&h=5&txtpad=1" alt="bar" />
[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 77% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb9900&w=77&h=5&txtpad=1" alt="bar" />
[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 | 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 | 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" />
[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 86% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=86&h=5&txtpad=1" alt="bar" />
[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 99% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ee55&w=99&h=5&txtpad=1" alt="bar" />
[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 99% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ee55&w=99&h=5&txtpad=1" alt="bar" />
[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 46% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa5500&w=46&h=5&txtpad=1" alt="bar" />
[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 81% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=81&h=5&txtpad=1" alt="bar" />
[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 93% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=93&h=5&txtpad=1" alt="bar" />
[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 81% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=81&h=5&txtpad=1" alt="bar" />
[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 81% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=81&h=5&txtpad=1" alt="bar" />
[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 96% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ee55&w=96&h=5&txtpad=1" alt="bar" />
[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 81% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=81&h=5&txtpad=1" alt="bar" />
[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 | 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 | 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 | 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 | 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 | 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 | 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) | 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) | 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) | 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) | 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>
<artifactId>authme</artifactId>
<version>5.2</version>
<version>5.3-SNAPSHOT</version>
<name>AuthMeReloaded</name>
<description>The first authentication plugin for the Bukkit API!</description>
@ -108,10 +108,6 @@
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</exclusion>
<exclusion>
<artifactId>json-simple</artifactId>
<groupId>com.googlecode.json-simple</groupId>
</exclusion>
<exclusion>
<artifactId>persistence-api</artifactId>
<groupId>javax.persistence</groupId>
@ -146,10 +142,6 @@
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</exclusion>
<exclusion>
<artifactId>json-simple</artifactId>
<groupId>com.googlecode.json-simple</groupId>
</exclusion>
<exclusion>
<artifactId>persistence-api</artifactId>
<groupId>javax.persistence</groupId>
@ -249,8 +241,8 @@
<shadedPattern>fr.xephi.authme.libs.jalu.injector</shadedPattern>
</relocation>
<relocation>
<pattern>com.github.authme.configme</pattern>
<shadedPattern>fr.xephi.authme.libs.authme.configme</shadedPattern>
<pattern>ch.jalu.configme</pattern>
<shadedPattern>fr.xephi.authme.libs.jalu.configme</shadedPattern>
</relocation>
<relocation>
<pattern>com.zaxxer.hikari</pattern>
@ -276,10 +268,10 @@
<pattern>javax.inject</pattern>
<shadedPattern>fr.xephi.authme.libs.javax.inject</shadedPattern>
</relocation>
<!-- MCStats.org metrics -->
<!-- bStats metrics class -->
<relocation>
<pattern>org.mcstats</pattern>
<shadedPattern>fr.xephi.authme</shadedPattern>
<pattern>org.bstats</pattern>
<shadedPattern>fr.xephi.authme.libs.org.bstats</shadedPattern>
</relocation>
</relocations>
<outputFile>target/${project.finalName}-spigot.jar</outputFile>
@ -331,10 +323,10 @@
<pattern>javax.inject</pattern>
<shadedPattern>fr.xephi.authme.libs.javax.inject</shadedPattern>
</relocation>
<!-- MCStats.org metrics -->
<!-- bStats metrics class -->
<relocation>
<pattern>org.mcstats</pattern>
<shadedPattern>fr.xephi.authme</shadedPattern>
<pattern>org.bstats</pattern>
<shadedPattern>fr.xephi.authme.libs.org.bstats</shadedPattern>
</relocation>
</relocations>
<outputFile>target/${project.finalName}-legacy.jar</outputFile>
@ -442,6 +434,12 @@
<id>xephi-repo</id>
<url>http://ci.xephi.fr/plugin/repository/everything/</url>
</repository>
<!-- bStats Repo -->
<repository>
<id>bstats-repo</id>
<url>http://repo.bstats.org/content/groups/public</url>
</repository>
</repositories>
<dependencies>
@ -451,7 +449,7 @@
<dependency>
<groupId>ch.jalu</groupId>
<artifactId>injector</artifactId>
<version>0.3</version>
<version>0.4</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
@ -513,7 +511,7 @@
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.5.1</version>
<version>2.6.0</version>
<scope>compile</scope>
<exclusions>
<exclusion>
@ -527,7 +525,7 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
<version>1.7.22</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
@ -544,19 +542,11 @@
<!-- Bukkit Libraries -->
<!-- Metrics API -->
<!-- bStats metrics -->
<dependency>
<groupId>org.mcstats.bukkit</groupId>
<artifactId>metrics</artifactId>
<version>R8-SNAPSHOT</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
</exclusion>
</exclusions>
<optional>true</optional>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>1.0</version>
</dependency>
<!-- ProtocolLib -->
@ -902,7 +892,7 @@
<dependency>
<groupId>ch.jalu</groupId>
<artifactId>configme</artifactId>
<version>0.3</version>
<version>0.4</version>
<scope>compile</scope>
<optional>true</optional>
<exclusions>
@ -933,7 +923,7 @@
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
<version>2.4.1</version>
<version>2.7.9</version>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>

View File

@ -14,6 +14,7 @@ import fr.xephi.authme.initialization.OnShutdownPlayerSaver;
import fr.xephi.authme.initialization.OnStartupTasks;
import fr.xephi.authme.initialization.SettingsProvider;
import fr.xephi.authme.initialization.TaskCloser;
import fr.xephi.authme.initialization.factory.FactoryDependencyHandler;
import fr.xephi.authme.listener.BlockListener;
import fr.xephi.authme.listener.EntityListener;
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.service.BackupService;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.GeoIpService;
import fr.xephi.authme.service.MigrationService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.task.CleanupTask;
import fr.xephi.authme.task.purge.PurgeService;
import fr.xephi.authme.util.PlayerUtils;
import org.apache.commons.lang.SystemUtils;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
@ -72,8 +72,6 @@ public class AuthMe extends JavaPlugin {
private DataSource database;
private BukkitService bukkitService;
private Injector injector;
private GeoIpService geoIpService;
private PlayerCache playerCache;
/**
* Constructor.
@ -139,6 +137,7 @@ public class AuthMe extends JavaPlugin {
initialize();
} catch (Exception e) {
ConsoleLogger.logException("Aborting initialization of AuthMe:", e);
OnStartupTasks.displayLegacyJarHint(e);
stopOrUnload();
return;
}
@ -197,11 +196,19 @@ public class AuthMe extends JavaPlugin {
ConsoleLogger.setLogger(getLogger());
ConsoleLogger.setLogFile(new File(getDataFolder(), LOG_FILENAME));
// Check java version
if(!SystemUtils.isJavaVersionAtLeast(1.8f)) {
throw new IllegalStateException("You need Java 1.8 or above to run this plugin!");
}
// Create plugin folder
getDataFolder().mkdir();
// Create injector, provide elements from the Bukkit environment and register providers
injector = new InjectorBuilder().addDefaultHandlers("fr.xephi.authme").create();
injector = new InjectorBuilder()
.addHandlers(new FactoryDependencyHandler())
.addDefaultHandlers("fr.xephi.authme")
.create();
injector.register(AuthMe.class, this);
injector.register(Server.class, getServer());
injector.register(PluginManager.class, getServer().getPluginManager());
@ -242,14 +249,13 @@ public class AuthMe extends JavaPlugin {
*/
protected void instantiateServices(Injector injector) {
// PlayerCache is still injected statically sometimes
playerCache = PlayerCache.getInstance();
PlayerCache playerCache = PlayerCache.getInstance();
injector.register(PlayerCache.class, playerCache);
database = injector.getSingleton(DataSource.class);
permsMan = injector.getSingleton(PermissionsManager.class);
bukkitService = injector.getSingleton(BukkitService.class);
commandHandler = injector.getSingleton(CommandHandler.class);
geoIpService = injector.getSingleton(GeoIpService.class);
// Trigger construction of API classes; they will keep track of the singleton
injector.getSingleton(NewAPI.class);
@ -270,6 +276,12 @@ public class AuthMe extends JavaPlugin {
&& settings.getProperty(PluginSettings.SESSIONS_ENABLED)) {
ConsoleLogger.warning("WARNING!!! You set session timeout to 0, this may cause security issues!");
}
// Use TLS property only affects port 25
if (!settings.getProperty(EmailSettings.PORT25_USE_TLS)
&& settings.getProperty(EmailSettings.SMTP_PORT) != 25) {
ConsoleLogger.warning("Note: You have set Email.useTls to false but this only affects mail over port 25");
}
}
/**
@ -344,24 +356,6 @@ public class AuthMe extends JavaPlugin {
ConsoleLogger.close();
}
public String replaceAllInfo(String message, Player player) {
String playersOnline = Integer.toString(bukkitService.getOnlinePlayers().size());
String ipAddress = PlayerUtils.getPlayerIp(player);
Server server = getServer();
return message
.replace("&", "\u00a7")
.replace("{PLAYER}", player.getName())
.replace("{ONLINE}", playersOnline)
.replace("{MAXPLAYERS}", Integer.toString(server.getMaxPlayers()))
.replace("{IP}", ipAddress)
.replace("{LOGINS}", Integer.toString(playerCache.getLogged()))
.replace("{WORLD}", player.getWorld().getName())
.replace("{SERVER}", server.getServerName())
.replace("{VERSION}", server.getBukkitVersion())
// TODO: We should cache info like this, maybe with a class that extends Player?
.replace("{COUNTRY}", geoIpService.getCountryName(ipAddress));
}
/**
* Handle Bukkit commands.
*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,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.UnregisterAdminCommand;
import fr.xephi.authme.command.executable.authme.VersionCommand;
import fr.xephi.authme.command.executable.authme.debug.DebugCommand;
import fr.xephi.authme.command.executable.captcha.CaptchaCommand;
import fr.xephi.authme.command.executable.changepassword.ChangePasswordCommand;
import fr.xephi.authme.command.executable.email.AddEmailCommand;
@ -37,6 +38,7 @@ import fr.xephi.authme.command.executable.register.RegisterCommand;
import fr.xephi.authme.command.executable.unregister.UnregisterCommand;
import fr.xephi.authme.permission.AdminPermission;
import fr.xephi.authme.permission.PlayerPermission;
import fr.xephi.authme.permission.PlayerStatePermission;
import java.util.Arrays;
import java.util.Collection;
@ -298,6 +300,19 @@ public class CommandInitializer {
.executableCommand(MessagesCommand.class)
.register();
CommandDescription.builder()
.parent(AUTHME_BASE)
.labels("debug", "dbg")
.description("Debug features")
.detailedDescription("Allows various operations for debugging.")
.withArgument("child", "The child to execute", true)
.withArgument(".", "meaning varies", true)
.withArgument(".", "meaning varies", true)
.withArgument(".", "meaning varies", true)
.permission(PlayerStatePermission.DEBUG_COMMAND)
.executableCommand(DebugCommand.class)
.register();
// Register the base login command
final CommandDescription LOGIN_BASE = CommandDescription.builder()
.parent(null)

View File

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

View File

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

View File

@ -11,10 +11,10 @@ import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.util.Utils;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.Collection;
import java.util.List;
/**
@ -44,8 +44,7 @@ public class ReloadCommand implements ExecutableCommand {
ConsoleLogger.setLoggingOptions(settings);
// We do not change database type for consistency issues, but we'll output a note in the logs
if (!settings.getProperty(DatabaseSettings.BACKEND).equals(dataSource.getType())) {
ConsoleLogger.info("Note: cannot change database type during /authme reload");
sender.sendMessage("Note: cannot change database type during /authme reload");
Utils.logAndSendMessage(sender, "Note: cannot change database type during /authme reload");
}
performReloadOnServices();
commonService.send(sender, MessageKey.CONFIG_RELOAD_SUCCESS);
@ -57,14 +56,10 @@ public class ReloadCommand implements ExecutableCommand {
}
private void performReloadOnServices() {
Collection<Reloadable> reloadables = injector.retrieveAllOfType(Reloadable.class);
for (Reloadable reloadable : reloadables) {
reloadable.reload();
}
injector.retrieveAllOfType(Reloadable.class)
.forEach(r -> r.reload());
Collection<SettingsDependent> settingsDependents = injector.retrieveAllOfType(SettingsDependent.class);
for (SettingsDependent dependent : settingsDependents) {
dependent.reload(settings);
}
injector.retrieveAllOfType(SettingsDependent.class)
.forEach(s -> s.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.data.CaptchaManager;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.limbo.LimboCache;
import fr.xephi.authme.message.MessageKey;
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.PlayerCache;
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.Messages;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.RecoveryCodeService;
import fr.xephi.authme.settings.properties.SecuritySettings;
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 javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
/**
* Command for password recovery by email.
*/
public class RecoverEmailCommand extends PlayerCommand {
public class RecoverEmailCommand extends PlayerCommand implements Reloadable {
@Inject
private PasswordSecurity passwordSecurity;
@ -37,17 +44,28 @@ public class RecoverEmailCommand extends PlayerCommand {
private PlayerCache playerCache;
@Inject
private SendMailSSL sendMailSsl;
private EmailService emailService;
@Inject
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
public void runCommand(Player player, List<String> arguments) {
protected void runCommand(Player player, List<String> arguments) {
final String playerMail = arguments.get(0);
final String playerName = player.getName();
if (!sendMailSsl.hasAllInformation()) {
if (!emailService.hasAllInformation()) {
ConsoleLogger.warning("Mail API is not set");
commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS);
return;
@ -78,15 +96,29 @@ public class RecoverEmailCommand extends PlayerCommand {
processRecoveryCode(player, arguments.get(1), email);
}
} else {
boolean maySendMail = checkEmailCooldown(player);
if (maySendMail) {
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) {
if (!checkEmailCooldown(player)) {
return;
}
String recoveryCode = recoveryCodeService.generateCode(player.getName());
boolean couldSendMail = sendMailSsl.sendRecoveryCode(player.getName(), email, recoveryCode);
boolean couldSendMail = emailService.sendRecoveryCode(player.getName(), email, recoveryCode);
if (couldSendMail) {
commonService.send(player, MessageKey.RECOVERY_CODE_SENT);
emailCooldown.add(player.getName().toLowerCase());
} else {
commonService.send(player, MessageKey.EMAIL_SEND_FAILURE);
}
@ -108,11 +140,22 @@ public class RecoverEmailCommand extends PlayerCommand {
HashedPassword hashNew = passwordSecurity.computeHash(thePass, name);
dataSource.updatePassword(name, hashNew);
boolean couldSendMail = sendMailSsl.sendPasswordMail(name, email, thePass);
boolean couldSendMail = emailService.sendPasswordMail(name, email, thePass);
if (couldSendMail) {
commonService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE);
emailCooldown.add(player.getName().toLowerCase());
} else {
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.command.PlayerCommand;
import fr.xephi.authme.mail.SendMailSSL;
import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
@ -37,7 +37,7 @@ public class RegisterCommand extends PlayerCommand {
private CommonService commonService;
@Inject
private SendMailSSL sendMailSsl;
private EmailService emailService;
@Inject
private ValidationService validationService;
@ -127,7 +127,7 @@ public class RegisterCommand extends PlayerCommand {
}
private void handleEmailRegistration(Player player, List<String> arguments) {
if (!sendMailSsl.hasAllInformation()) {
if (!emailService.hasAllInformation()) {
commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS);
ConsoleLogger.warning("Cannot register player '" + player.getName() + "': no email or password is set "
+ "to send emails from. Please adjust your config at " + EmailSettings.MAIL_ACCOUNT.getPath());

View File

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

View File

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

View File

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

View File

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

View File

@ -10,6 +10,9 @@ import org.bukkit.scheduler.BukkitTask;
*/
public class LimboPlayer {
public static final float DEFAULT_WALK_SPEED = 0.2f;
public static final float DEFAULT_FLY_SPEED = 0.1f;
private final boolean canFly;
private final boolean operator;
private final String group;

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

View File

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

View File

@ -45,7 +45,10 @@ public class MySQL implements DataSource {
private HikariDataSource ds;
private String phpBbPrefix;
private String IPBPrefix;
private int phpBbGroup;
private int IPBGroup;
private int XFGroup;
private String wordpressPrefix;
public MySQL(Settings settings) throws ClassNotFoundException, SQLException {
@ -96,10 +99,13 @@ public class MySQL implements DataSource {
this.hashAlgorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH);
this.phpBbPrefix = settings.getProperty(HooksSettings.PHPBB_TABLE_PREFIX);
this.phpBbGroup = settings.getProperty(HooksSettings.PHPBB_ACTIVATED_GROUP_ID);
this.IPBPrefix = settings.getProperty(HooksSettings.IPB_TABLE_PREFIX);
this.IPBGroup = settings.getProperty(HooksSettings.IPB_ACTIVATED_GROUP_ID);
this.XFGroup = settings.getProperty(HooksSettings.XF_ACTIVATED_GROUP_ID);
this.wordpressPrefix = settings.getProperty(HooksSettings.WORDPRESS_TABLE_PREFIX);
this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE);
if (poolSize == -1) {
poolSize = Utils.getCoreCount();
poolSize = Utils.getCoreCount()*3;
}
this.useSSL = settings.getProperty(DatabaseSettings.MYSQL_USE_SSL);
}
@ -334,8 +340,39 @@ public class MySQL implements DataSource {
pst.close();
}
}
if (hashAlgorithm == HashAlgorithm.PHPBB) {
if (hashAlgorithm == HashAlgorithm.IPB4){
sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
pst = con.prepareStatement(sql);
pst.setString(1, auth.getNickname());
rs = pst.executeQuery();
if (rs.next()){
// Update player group in core_members
sql = "UPDATE " + IPBPrefix + tableName + " SET "+ tableName + ".member_group_id=? WHERE " + col.NAME + "=?;";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, IPBGroup);
pst2.setString(2, auth.getNickname());
pst2.executeUpdate();
pst2.close();
// Get current time without ms
long time = System.currentTimeMillis() / 1000;
// update joined date
sql = "UPDATE " + IPBPrefix + tableName + " SET "+ tableName + ".joined=? WHERE " + col.NAME + "=?;";
pst2 = con.prepareStatement(sql);
pst2.setLong(1, time);
pst2.setString(2, auth.getNickname());
pst2.executeUpdate();
pst2.close();
// Update last_visit
sql = "UPDATE " + IPBPrefix + tableName + " SET " + tableName + ".last_visit=? WHERE " + col.NAME + "=?;";
pst2 = con.prepareStatement(sql);
pst2.setLong(1, time);
pst2.setString(2, auth.getNickname());
pst2.executeUpdate();
pst2.close();
}
rs.close();
pst.close();
} else if (hashAlgorithm == HashAlgorithm.PHPBB) {
sql = "SELECT " + col.ID + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
pst = con.prepareStatement(sql);
pst.setString(1, auth.getNickname());
@ -479,6 +516,7 @@ public class MySQL implements DataSource {
rs = pst.executeQuery();
if (rs.next()) {
int id = rs.getInt(col.ID);
// Insert player password, salt in xf_user_authenticate
sql = "INSERT INTO xf_user_authenticate (user_id, scheme_class, data) VALUES (?,?,?)";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, id);
@ -490,6 +528,39 @@ public class MySQL implements DataSource {
pst2.setBlob(3, blob);
pst2.executeUpdate();
pst2.close();
// Update player group in xf_users
sql = "UPDATE " + tableName + " SET "+ tableName + ".user_group_id=? WHERE " + col.NAME + "=?;";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, XFGroup);
pst2.setString(2, auth.getNickname());
pst2.executeUpdate();
pst2.close();
// Update player permission combination in xf_users
sql = "UPDATE " + tableName + " SET "+ tableName + ".permission_combination_id=? WHERE " + col.NAME + "=?;";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, XFGroup);
pst2.setString(2, auth.getNickname());
pst2.executeUpdate();
pst2.close();
// Insert player privacy combination in xf_user_privacy
sql = "INSERT INTO xf_user_privacy (user_id, allow_view_profile, allow_post_profile, allow_send_personal_conversation, allow_view_identities, allow_receive_news_feed) VALUES (?,?,?,?,?,?)";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, id);
pst2.setString(2, "everyone");
pst2.setString(3, "members");
pst2.setString(4, "members");
pst2.setString(5, "everyone");
pst2.setString(6, "everyone");
pst2.executeUpdate();
pst2.close();
// Insert player group relation in xf_user_group_relation
sql = "INSERT INTO xf_user_group_relation (user_id, user_group_id, is_primary) VALUES (?,?,?)";
pst2 = con.prepareStatement(sql);
pst2.setInt(1, id);
pst2.setInt(2, XFGroup);
pst2.setString(3, "1");
pst2.executeUpdate();
pst2.close();
}
rs.close();
pst.close();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,13 @@
package fr.xephi.authme.initialization;
import ch.jalu.injector.exceptions.InjectorReflectionException;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import org.bstats.Metrics;
import fr.xephi.authme.output.ConsoleFilter;
import fr.xephi.authme.output.Log4JFilter;
import fr.xephi.authme.service.BukkitService;
@ -18,10 +20,9 @@ import fr.xephi.authme.util.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.mcstats.Metrics;
import javax.inject.Inject;
import java.io.IOException;
import java.util.Optional;
import java.util.logging.Logger;
import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE;
@ -44,34 +45,28 @@ public class OnStartupTasks {
OnStartupTasks() {
}
/**
* Sends bstats metrics.
*
* @param plugin the plugin instance
* @param settings the settings
*/
public static void sendMetrics(AuthMe plugin, Settings settings) {
try {
final Metrics metrics = new Metrics(plugin);
final Metrics.Graph languageGraph = metrics.createGraph("Messages Language");
final String messagesLanguage = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
languageGraph.addPlotter(new Metrics.Plotter(messagesLanguage) {
metrics.addCustomChart(new Metrics.SimplePie("messages_language") {
@Override
public int getValue() {
return 1;
public String getValue() {
return settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
}
});
final Metrics.Graph databaseBackend = metrics.createGraph("Database Backend");
final String dataSource = settings.getProperty(DatabaseSettings.BACKEND).toString();
databaseBackend.addPlotter(new Metrics.Plotter(dataSource) {
metrics.addCustomChart(new Metrics.SimplePie("database_backend") {
@Override
public int getValue() {
return 1;
public String getValue() {
return settings.getProperty(DatabaseSettings.BACKEND).toString();
}
});
// Submit metrics
metrics.start();
} catch (IOException e) {
// Failed to submit the metrics data
ConsoleLogger.logException("Can't send Metrics data! The plugin will work anyway...", e);
}
}
/**
@ -124,4 +119,23 @@ public class OnStartupTasks {
}
}, 1, TICKS_PER_MINUTE * settings.getProperty(EmailSettings.DELAY_RECALL));
}
/**
* Displays a hint to use the legacy AuthMe JAR if AuthMe could not be started
* because Gson was not found.
*
* @param e the exception to process
*/
public static void displayLegacyJarHint(Exception e) {
if (e instanceof InjectorReflectionException) {
Throwable causeOfCause = Optional.of(e)
.map(Throwable::getCause)
.map(Throwable::getCause).orElse(null);
if (causeOfCause instanceof NoClassDefFoundError
&& "Lcom/google/gson/Gson;".equals(causeOfCause.getMessage())) {
ConsoleLogger.warning("YOU MUST DOWNLOAD THE LEGACY JAR TO USE AUTHME ON YOUR SERVER");
ConsoleLogger.warning("Get authme-legacy.jar from http://ci.xephi.fr/job/AuthMeReloaded/");
}
}
}
}

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,6 +10,12 @@ import org.bukkit.plugin.RegisteredServiceProvider;
import java.util.Arrays;
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 {
private Permission vaultProvider;

View File

@ -6,10 +6,15 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.tyrannyofheaven.bukkit.zPermissions.ZPermissionsService;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
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 {
private ZPermissionsService zPermissionsService;
@ -25,7 +30,8 @@ public class ZPermissionsHandler implements PermissionHandler {
@Override
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
@ -36,31 +42,29 @@ public class ZPermissionsHandler implements PermissionHandler {
@Override
public boolean hasPermissionOffline(String name, PermissionNode node) {
Map<String, Boolean> perms = zPermissionsService.getPlayerPermissions(null, null, name);
if (perms.containsKey(node.getNode()))
if (perms.containsKey(node.getNode())) {
return perms.get(node.getNode());
else
} else {
return false;
}
@Override
public boolean isInGroup(Player player, String group) {
return getGroups(player).contains(group);
}
@Override
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
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
public List<String> getGroups(Player player) {
public Collection<String> getGroups(Player player) {
// TODO Gnat008 20160631: Use UUID not name?
return new ArrayList<String>(zPermissionsService.getPlayerGroups(player.getName()));
return zPermissionsService.getPlayerGroups(player.getName());
}
@Override

View File

@ -1,6 +1,5 @@
package fr.xephi.authme.process.join;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.SessionManager;
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.datasource.DataSource;
import fr.xephi.authme.events.ProtectInventoryEvent;
import fr.xephi.authme.service.PluginHookService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.AuthGroupType;
import fr.xephi.authme.permission.PlayerStatePermission;
import fr.xephi.authme.process.AsynchronousProcess;
import fr.xephi.authme.service.CommonService;
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.properties.HooksSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.task.LimboPlayerTaskManager;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.GameMode;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
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.settings.properties.RestrictionSettings.PROTECT_INVENTORY_BEFORE_LOGIN;
/**
* 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 {
@Inject
private AuthMe plugin;
private Server server;
@Inject
private DataSource database;
@ -71,6 +72,9 @@ public class AsynchronousJoin implements AsynchronousProcess {
@Inject
private CommandManager commandManager;
@Inject
private ValidationService validationService;
AsynchronousJoin() {
}
@ -91,13 +95,13 @@ public class AsynchronousJoin implements AsynchronousProcess {
pluginHookService.setEssentialsSocialSpyStatus(player, false);
}
if (isNameRestricted(name, ip, player.getAddress().getHostName())) {
if (!validationService.fulfillsNameRestrictions(player)) {
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(new Runnable() {
@Override
public void run() {
player.kickPlayer(service.retrieveSingleMessage(MessageKey.NOT_OWNER_ERROR));
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) {
limboCache.addPlayerData(player);
service.setGroup(player, AuthGroupType.NOT_LOGGED_IN);
service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED);
// Protect inventory
if (service.getProperty(PROTECT_INVENTORY_BEFORE_LOGIN)) {
@ -130,14 +134,16 @@ public class AsynchronousJoin implements AsynchronousProcess {
PlayerAuth auth = database.getAuth(name);
database.setUnlogged(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);
bukkitService.runTaskOptionallyAsync(() -> asynchronousLogin.forceLogin(player));
return;
} else if (service.getProperty(PluginSettings.SESSIONS_EXPIRE_ON_IP_CHANGE)) {
} else {
service.send(player, MessageKey.SESSION_EXPIRED);
}
}
}
} else {
// Not Registered. Delete old data, load default one.
limboCache.deletePlayerData(player);
@ -178,36 +184,6 @@ public class AsynchronousJoin implements AsynchronousProcess {
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
* 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.message.MessageKey;
import fr.xephi.authme.permission.AdminPermission;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.permission.PlayerPermission;
import fr.xephi.authme.permission.PlayerStatePermission;
import fr.xephi.authme.process.AsynchronousProcess;
@ -46,9 +45,6 @@ public class AsynchronousLogin implements AsynchronousProcess {
@Inject
private CommonService service;
@Inject
private PermissionsManager permissionsManager;
@Inject
private PlayerCache playerCache;
@ -269,8 +265,8 @@ public class AsynchronousLogin implements AsynchronousProcess {
}
bukkitService.dispatchConsoleCommand(command
.replaceAll("%playername%", player.getName())
.replaceAll("%playerip%", ip)
.replace("%playername%", player.getName())
.replace("%playerip%", ip)
);
}
@ -300,10 +296,10 @@ public class AsynchronousLogin implements AsynchronousProcess {
for (Player onlinePlayer : bukkitService.getOnlinePlayers()) {
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()));
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,
player.getName(), Integer.toString(auths.size()));
onlinePlayer.sendMessage(message);
@ -323,7 +319,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
boolean hasReachedMaxLoggedInPlayersForIp(Player player, String ip) {
// 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
|| permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)
|| service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)
|| "127.0.0.1".equalsIgnoreCase(ip)
|| "localhost".equalsIgnoreCase(ip)) {
return false;

View File

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

View File

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

View File

@ -1,6 +1,6 @@
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.process.SynchronousProcess;
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.message.MessageKey;
import fr.xephi.authme.permission.AuthGroupType;
import fr.xephi.authme.service.CommonService;
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.util.PlayerUtils;
import org.bukkit.entity.Player;
@ -25,12 +24,10 @@ public class ProcessSyncEmailRegister implements SynchronousProcess {
}
public void processEmailRegister(Player player) {
final String name = player.getName().toLowerCase();
if (!service.getProperty(HooksSettings.REGISTERED_GROUP).isEmpty()) {
service.setGroup(player, AuthGroupType.REGISTERED);
}
service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED);
service.send(player, MessageKey.ACCOUNT_NOT_ACTIVATED);
final String name = player.getName().toLowerCase();
limboPlayerTaskManager.registerTimeoutTask(player);
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.message.MessageKey;
import fr.xephi.authme.permission.AuthGroupType;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.process.SynchronousProcess;
import fr.xephi.authme.service.BungeeService;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.commandconfig.CommandManager;
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.task.LimboPlayerTaskManager;
import fr.xephi.authme.util.PlayerUtils;
@ -56,10 +55,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess {
}
public void processPasswordRegister(Player player) {
if (!service.getProperty(HooksSettings.REGISTERED_GROUP).isEmpty()) {
service.setGroup(player, AuthGroupType.REGISTERED);
}
service.setGroup(player, AuthGroupType.REGISTERED_UNAUTHENTICATED);
service.send(player, MessageKey.REGISTER_SUCCESS);
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.datasource.DataSource;
import fr.xephi.authme.mail.SendMailSSL;
import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.SyncProcessManager;
@ -15,8 +15,8 @@ import org.bukkit.entity.Player;
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.process.register.executors.PlayerAuthBuilderHelper.createPlayerAuth;
import static fr.xephi.authme.settings.properties.EmailSettings.RECOVERY_PASSWORD_LENGTH;
/**
@ -34,7 +34,7 @@ class EmailRegisterExecutorProvider {
private CommonService commonService;
@Inject
private SendMailSSL sendMailSsl;
private EmailService emailService;
@Inject
private SyncProcessManager syncProcessManager;
@ -80,7 +80,7 @@ class EmailRegisterExecutorProvider {
@Override
public void executePostPersistAction() {
boolean couldSendMail = sendMailSsl.sendPasswordMail(player.getName(), email, password);
boolean couldSendMail = emailService.sendPasswordMail(player.getName(), email, password);
if (couldSendMail) {
syncProcessManager.processSyncEmailRegister(player);
} else {

View File

@ -56,10 +56,10 @@ class PasswordRegisterExecutorProvider {
/** Registration executor for password registration. */
class PasswordRegisterExecutor implements RegistrationExecutor {
protected final Player player;
private final Player player;
private final String password;
private final String email;
protected HashedPassword hashedPassword;
private HashedPassword hashedPassword;
/**
* Constructor.
@ -105,6 +105,14 @@ class PasswordRegisterExecutorProvider {
}
syncProcessManager.processSyncPasswordRegister(player);
}
protected Player getPlayer() {
return player;
}
protected HashedPassword getHashedPassword() {
return hashedPassword;
}
}
/** Executor for password registration via API call. */
@ -147,8 +155,9 @@ class PasswordRegisterExecutorProvider {
public void executePostPersistAction() {
super.executePostPersistAction();
String qrCodeUrl = TwoFactor.getQRBarcodeURL(player.getName(), Bukkit.getIp(), hashedPassword.getHash());
commonService.send(player, MessageKey.TWO_FACTOR_CREATE, hashedPassword.getHash(), qrCodeUrl);
String hash = getHashedPassword().getHash();
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;
import ch.jalu.injector.Injector;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.PasswordEncryptionEvent;
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.HashedPassword;
import fr.xephi.authme.settings.Settings;
@ -29,7 +29,7 @@ public class PasswordSecurity implements Reloadable {
private PluginManager pluginManager;
@Inject
private Injector injector;
private Factory<EncryptionMethod> hashAlgorithmFactory;
private HashAlgorithm algorithm;
private Collection<HashAlgorithm> legacyAlgorithms;
@ -154,7 +154,7 @@ public class PasswordSecurity implements Reloadable {
if (HashAlgorithm.CUSTOM.equals(algorithm) || HashAlgorithm.PLAINTEXT.equals(algorithm)) {
return null;
}
return injector.newInstance(algorithm.getClazz());
return hashAlgorithmFactory.newInstance(algorithm.getClazz());
}
private void hashPasswordForNewAlgorithm(String password, String playerName) {

View File

@ -8,15 +8,15 @@ public abstract class SeparateSaltMethod implements EncryptionMethod {
@Override
public abstract String computeHash(String password, String salt, String name);
@Override
public abstract String generateSalt();
@Override
public HashedPassword computeHash(String password, String name) {
String salt = generateSalt();
return new HashedPassword(computeHash(password, salt, name), salt);
}
@Override
public abstract String generateSalt();
@Override
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
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);
@Override
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
return hashedPassword.getHash().equals(computeHash(password, name).getHash());
public String computeHash(String password, String salt, String name) {
return computeHash(password, name).getHash();
}
@Override
public String computeHash(String password, String salt, String name) {
return computeHash(password, name).getHash();
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
return hashedPassword.getHash().equals(computeHash(password, name).getHash());
}
@Override

View File

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

View File

@ -13,7 +13,6 @@ import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
@ -172,7 +171,7 @@ public class BukkitService implements SettingsDependent {
* @return a BukkitTask that contains the id number
* @throws IllegalArgumentException if plugin is null
* @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) {
return task.runTaskTimer(authMe, delay, period);

View File

@ -65,16 +65,6 @@ public class CommonService {
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.
*
@ -101,10 +91,10 @@ public class CommonService {
*
* @param player the player to process
* @param group the group to add the player to
* @return true on success, false otherwise
*/
public boolean setGroup(Player player, AuthGroupType group) {
return authGroupHandler.setGroup(player, group);
// TODO ljacqu 20170304: Move this out of CommonService
public void setGroup(Player player, AuthGroupType group) {
authGroupHandler.setGroup(player, group);
}
}

View File

@ -1,38 +1,38 @@
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.util.RandomStringUtils;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.util.expiring.ExpiringMap;
import javax.inject.Inject;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
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.
*/
public class RecoveryCodeService implements SettingsDependent {
private Map<String, ExpiringEntry> recoveryCodes = new ConcurrentHashMap<>();
public class RecoveryCodeService implements SettingsDependent, HasCleanup {
private final ExpiringMap<String, String> recoveryCodes;
private int recoveryCodeLength;
private long recoveryCodeExpirationMillis;
private int recoveryCodeExpiration;
@Inject
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
*/
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) {
String code = RandomStringUtils.generateHex(recoveryCodeLength);
recoveryCodes.put(player, new ExpiringEntry(code, System.currentTimeMillis() + recoveryCodeExpirationMillis));
recoveryCodes.put(player, code);
return code;
}
@ -55,11 +55,8 @@ public class RecoveryCodeService implements SettingsDependent {
* @return true if the code matches and has not expired, false otherwise
*/
public boolean isCodeValid(String player, String code) {
ExpiringEntry entry = recoveryCodes.get(player);
if (entry != null) {
return code != null && code.equals(entry.getCode());
}
return false;
String storedCode = recoveryCodes.get(player);
return storedCode != null && storedCode.equals(code);
}
/**
@ -74,26 +71,12 @@ public class RecoveryCodeService implements SettingsDependent {
@Override
public void reload(Settings settings) {
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);
}
/**
* Entry with an expiration.
*/
@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;
@Override
public void performCleanup() {
recoveryCodes.removeExpiredEntries();
}
}
}

View File

@ -1,6 +1,9 @@
package fr.xephi.authme.service;
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.initialization.Reloadable;
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.RestrictionSettings;
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 org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
@ -23,6 +27,8 @@ import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import static fr.xephi.authme.util.StringUtils.isInsideString;
/**
* Validation service.
*/
@ -39,6 +45,7 @@ public class ValidationService implements Reloadable {
private Pattern passwordRegex;
private Set<String> unrestrictedNames;
private Multimap<String, String> restrictedNames;
ValidationService() {
}
@ -49,6 +56,9 @@ public class ValidationService implements Reloadable {
passwordRegex = Utils.safePatternCompile(settings.getProperty(RestrictionSettings.ALLOWED_PASSWORD_REGEX));
// Use Set for more efficient contains() lookup
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);
return validateWhitelistAndBlacklist(countryCode,
ProtectionSettings.COUNTRIES_WHITELIST,
ProtectionSettings.COUNTRIES_BLACKLIST);
boolean isCountryAllowed = validateWhitelistAndBlacklist(countryCode,
ProtectionSettings.COUNTRIES_WHITELIST, 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());
}
/**
* 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.
* 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,
Property<List<String>> blacklist) {
List<String> whitelistValue = settings.getProperty(whitelist);
if (!CollectionUtils.isEmpty(whitelistValue)) {
if (!Utils.isCollectionEmpty(whitelistValue)) {
return containsIgnoreCase(whitelistValue, value);
}
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) {
@ -160,6 +189,26 @@ public class ValidationService implements Reloadable {
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 {
private final MessageKey messageKey;
private final String[] args;
@ -195,6 +244,7 @@ public class ValidationService implements Reloadable {
public MessageKey getMessageKey() {
return messageKey;
}
public String[] getArgs() {
return args;
}

View File

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

View File

@ -71,6 +71,7 @@ public class SettingsMigrationService extends PlainMigrationService {
| hasOldHelpHeaderProperty(resource)
| hasSupportOldPasswordProperty(resource)
| convertToRegistrationType(resource)
| mergeAndMovePermissionGroupSettings(resource)
|| hasDeprecatedProperties(resource);
}
@ -81,7 +82,8 @@ public class SettingsMigrationService extends PlainMigrationService {
"VeryGames", "settings.restrictions.allowAllCommandsIfRegistrationIsOptional", "DataSource.mySQLWebsite",
"Hooks.customAttributes", "Security.stop.kickPlayersBeforeStopping",
"settings.restrictions.keepCollisionsDisabled", "settings.forceCommands", "settings.forceCommandsAsConsole",
"settings.forceRegisterCommands", "settings.forceRegisterCommandsAsConsole"};
"settings.forceRegisterCommands", "settings.forceRegisterCommandsAsConsole",
"settings.sessions.sessionExpireOnIpChange"};
for (String deprecatedPath : deprecatedProperties) {
if (resource.contains(deprecatedPath)) {
return true;
@ -251,6 +253,26 @@ public class SettingsMigrationService extends PlainMigrationService {
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.
*
@ -264,9 +286,10 @@ public class SettingsMigrationService extends PlainMigrationService {
Property<T> newProperty,
PropertyResource resource) {
if (resource.contains(oldProperty.getPath())) {
if (resource.contains(newProperty.getPath())) {
ConsoleLogger.info("Detected deprecated property " + oldProperty.getPath());
if (!resource.contains(newProperty.getPath())) {
ConsoleLogger.info("Renamed " + oldProperty.getPath() + " to " + newProperty.getPath());
} else {
ConsoleLogger.info("Renaming " + oldProperty.getPath() + " to " + newProperty.getPath());
resource.setValue(newProperty.getPath(), oldProperty.getValue(resource));
}
return true;

View File

@ -1,10 +1,9 @@
package fr.xephi.authme.settings;
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.Reloadable;
import fr.xephi.authme.service.PluginHookService;
import fr.xephi.authme.settings.properties.HooksSettings;
import fr.xephi.authme.settings.properties.RestrictionSettings;
import fr.xephi.authme.util.FileUtils;
@ -43,11 +42,9 @@ public class SpawnLoader implements Reloadable {
* @param pluginFolder The AuthMe data folder
* @param settings The setting instance
* @param pluginHookService The plugin hooks instance
* @param dataSource The plugin auth database instance
*/
@Inject
SpawnLoader(@DataFolder File pluginFolder, Settings settings, PluginHookService pluginHookService,
DataSource dataSource) {
SpawnLoader(@DataFolder File pluginFolder, Settings settings, PluginHookService pluginHookService) {
// TODO ljacqu 20160312: Check if resource could be copied and handle the case if not
File spawnFile = new File(pluginFolder, "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.Reloadable;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.GeoIpService;
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 javax.inject.Inject;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static fr.xephi.authme.util.lazytags.TagBuilder.createTag;
/**
* 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 BukkitService bukkitService;
private final GeoIpService geoIpService;
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
CommandManager(@DataFolder File dataFolder, BukkitService bukkitService,
CommandManager(@DataFolder File dataFolder, BukkitService bukkitService, GeoIpService geoIpService,
CommandMigrationService commandMigrationService) {
this.dataFolder = dataFolder;
this.bukkitService = bukkitService;
this.geoIpService = geoIpService;
this.commandMigrationService = commandMigrationService;
reload();
}
@ -38,7 +51,7 @@ public class CommandManager implements Reloadable {
* @param player the joining 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
*/
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
*/
public void runCommandsOnLogin(Player player) {
executeCommands(player, commandConfig.getOnLogin());
executeCommands(player, onLoginCommands.getAdaptedItems(player));
}
private void executeCommands(Player player, Map<String, Command> commands) {
for (Command command : commands.values()) {
final String execution = command.getCommand().replace("%p", player.getName());
private void executeCommands(Player player, List<Command> commands) {
for (Command command : commands) {
final String execution = command.getCommand();
if (Executor.CONSOLE.equals(command.getExecutor())) {
bukkitService.dispatchConsoleCommand(execution);
} else {
@ -77,8 +90,22 @@ public class CommandManager implements Reloadable {
SettingsManager settingsManager = new SettingsManager(
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() {
String[] comments = {
"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:",
"onRegister:",
" welcome:",

View File

@ -6,7 +6,7 @@ import ch.jalu.configme.properties.Property;
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")
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;
public class ConverterSettings implements SettingsHolder {
public final class ConverterSettings implements SettingsHolder {
@Comment("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;
public class DatabaseSettings implements SettingsHolder {
public final class DatabaseSettings implements SettingsHolder {
@Comment({"What type of database do you want to use?",
"Valid values: sqlite, mysql"})
"Valid values: SQLITE, MYSQL"})
public static final Property<DataSourceType> BACKEND =
newProperty(DataSourceType.class, "DataSource.backend", DataSourceType.SQLITE);
@ -26,11 +26,15 @@ public class DatabaseSettings implements SettingsHolder {
public static final Property<String> MYSQL_PORT =
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 =
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 =
newProperty("DataSource.mySQLPassword", "12345");
@ -58,10 +62,6 @@ public class DatabaseSettings implements SettingsHolder {
public static final Property<String> MYSQL_COL_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")
public static final Property<String> MYSQL_COL_SALT =
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.newProperty;
public class EmailSettings implements SettingsHolder {
public final class EmailSettings implements SettingsHolder {
@Comment("Email SMTP server host")
public static final Property<String> SMTP_HOST =
@ -19,6 +19,10 @@ public class EmailSettings implements SettingsHolder {
public static final Property<Integer> SMTP_PORT =
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")
public static final Property<String> MAIL_ACCOUNT =
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.newProperty;
public class HooksSettings implements SettingsHolder {
public final class HooksSettings implements SettingsHolder {
@Comment("Do we need to hook with multiverse for spawn checking?")
public static final Property<Boolean> MULTIVERSE =
@ -54,17 +54,22 @@ public class HooksSettings implements SettingsHolder {
public static final Property<Integer> PHPBB_ACTIVATED_GROUP_ID =
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")
public static final Property<String> WORDPRESS_TABLE_PREFIX =
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() {
}

View File

@ -7,7 +7,7 @@ import fr.xephi.authme.output.LogLevel;
import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public class PluginSettings implements SettingsHolder {
public final class PluginSettings implements SettingsHolder {
@Comment({
"Do you want to enable the session feature?",
@ -22,20 +22,11 @@ public class PluginSettings implements SettingsHolder {
@Comment({
"After how many minutes should a session expire?",
"Remember that sessions will end only after the timeout, and",
"if the player's IP has changed but the timeout hasn't expired,",
"the player will be kicked from the server due to invalid session"
"A player's session ends after the timeout or if his IP has changed"
})
public static final Property<Integer> SESSIONS_TIMEOUT =
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({
"Message language, available languages:",
"https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md"
@ -44,13 +35,33 @@ public class PluginSettings implements SettingsHolder {
newProperty("settings.messagesLanguage", "en");
@Comment({
"Take care with this option; if you want",
"to use group switching of AuthMe",
"for unloggedIn players, set this setting to true.",
"Default is false."
"Enables switching a player to defined permission groups before they log in.",
"See below for a detailed explanation."
})
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({
"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;
public class ProtectionSettings implements SettingsHolder {
public final class ProtectionSettings implements SettingsHolder {
@Comment("Enable some servers protection (country based login, antibot)")
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;
public class PurgeSettings implements SettingsHolder {
public final class PurgeSettings implements SettingsHolder {
@Comment("If enabled, AuthMe automatically purges old, unused accounts")
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;
public class RegistrationSettings implements SettingsHolder {
public final class RegistrationSettings implements SettingsHolder {
@Comment("Enable registration on the server?")
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"
})
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({
"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.newProperty;
public class RestrictionSettings implements SettingsHolder {
public final class RestrictionSettings implements SettingsHolder {
@Comment({
"Can not authenticated players chat?",
@ -81,9 +81,13 @@ public class RestrictionSettings implements SettingsHolder {
"Example:",
" AllowedRestrictedUser:",
" - 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");
@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?")
public static final Property<Boolean> KICK_NON_REGISTERED =
newProperty("settings.restrictions.kickNonRegistered", false);
@ -115,7 +119,7 @@ public class RestrictionSettings implements SettingsHolder {
public static final Property<Integer> TIMEOUT =
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 =
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 =
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")
public static final Property<String> SPAWN_PRIORITY =
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.newProperty;
public class SecuritySettings implements SettingsHolder {
public final class SecuritySettings implements SettingsHolder {
@Comment({"Stop the server if we can't contact the sql database",
"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 =
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")
public static final Property<Integer> MIN_PASSWORD_LENGTH =
newProperty("settings.security.minPasswordLength", 5);
@ -48,22 +52,6 @@ public class SecuritySettings implements SettingsHolder {
public static final Property<Integer> MAX_PASSWORD_LENGTH =
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({
"Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512, WHIRLPOOL,",
"MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB,",
@ -98,7 +86,8 @@ public class SecuritySettings implements SettingsHolder {
"- 'password'",
"- 'help'"})
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")
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 =
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() {
}

View File

@ -3,20 +3,19 @@ package fr.xephi.authme.task.purge;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.PurgeSettings;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.Utils;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import javax.inject.Inject;
import java.util.Calendar;
import java.util.Collection;
import java.util.Set;
// TODO: move into services. -sgdc3
import static fr.xephi.authme.util.Utils.logAndSendMessage;
/**
* Initiates purge tasks.
@ -75,7 +74,7 @@ public class PurgeService {
public void runPurge(CommandSender sender, long until, boolean includeEntriesWithLastLoginZero) {
//todo: note this should may run async because it may executes a SQL-Query
Set<String> toPurge = dataSource.getRecordsToPurge(until, includeEntriesWithLastLoginZero);
if (CollectionUtils.isEmpty(toPurge)) {
if (Utils.isCollectionEmpty(toPurge)) {
logAndSendMessage(sender, "No players to purge");
return;
}
@ -119,12 +118,4 @@ public class PurgeService {
void executePurge(Collection<OfflinePlayer> players, Collection<String> 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.
*/
public class PlayerUtils {
public final class PlayerUtils {
// Utility class
private PlayerUtils() {

View File

@ -8,7 +8,7 @@ import java.util.Random;
*/
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 int HEX_MAX_INDEX = 16;
private static final int LOWER_ALPHANUMERIC_INDEX = 36;
@ -24,7 +24,7 @@ public final class RandomStringUtils {
* @return The random string
*/
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
*/
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
*/
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) {
throw new IllegalArgumentException("Length must be positive but was " + length);
}
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; ++i) {
sb.append(CHARS.charAt(RANDOM.nextInt(maxIndex)));
sb.append(CHARS[RANDOM.nextInt(maxIndex)]);
}
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
*/
public static boolean containsAny(String str, String... pieces) {
public static boolean containsAny(String str, Iterable<String> pieces) {
if (str == null) {
return false;
}
@ -76,4 +76,20 @@ public final class StringUtils {
public static String formatException(Throwable th) {
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;
import fr.xephi.authme.ConsoleLogger;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import java.util.Collection;
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.
*

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