Sync repo files to b22

This commit is contained in:
HaHaWTH 2023-09-20 02:15:22 +08:00
parent ed320196fe
commit 3f6a7ce48f
478 changed files with 94676 additions and 0 deletions

203
.checkstyle.xml Normal file
View File

@ -0,0 +1,203 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!--
Note: Checkstyle likes to introduce breaking changes like removing support for certain properties. Since this
configuration is also used by CodeClimate, we should keep it in sync with the version that they use:
https://github.com/codeclimate/codeclimate-checkstyle/blob/master/bin/install-checkstyle.sh
https://docs.codeclimate.com/docs/checkstyle (currently outdated)
-->
<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="warning"/>
<property name="fileExtensions" value="java"/>
<module name="SuppressWarningsFilter" />
<module name="LineLength">
<property name="max" value="120"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<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="TodoComment">
<!-- Allow comments which have an issue number as they are accounted for in the issue tracker -->
<property name="format" value="TODO(?! #\d+:)|FIXME"/>
</module>
<module name="GenericWhitespace"/>
<module name="WhitespaceAfter"/>
<module name="AvoidStarImport"/>
<module name="RedundantImport"/>
<module name="UnusedImports"/>
<module name="OneTopLevelClass"/>
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InnerTypeLast"/>
<module name="VisibilityModifier"/>
<module name="AvoidNestedBlocks"/>
<module name="NoLineWrap"/>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<module name="RightCurly">
<property name="option" value="alone"/>
<property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT"/>
</module>
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
<module name="MissingSwitchDefault"/>
<module name="DefaultComesLast"/>
<module name="FallThrough"/>
<module name="NestedTryDepth"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<property name="allowOneCharVarInForLoop" value="true"/>
<message key="name.invalidPattern"
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="NoFinalizer"/>
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="1"/>
</module>
<module name="DeclarationOrder"/>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="MethodParamPad"/>
<module name="StringLiteralEquality"/>
<module name="BooleanExpressionComplexity">
<property name="max" value="5"/>
</module>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR "/>
</module>
<module name="CyclomaticComplexity">
<property name="max" value="15"/>
</module>
<module name="JavaNCSS">
<property name="methodMaximum" value="40"/>
<property name="classMaximum" value="1000"/>
</module>
<module name="AnnotationLocation">
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="NonEmptyAtclauseDescription"/>
<module name="JavadocTagContinuationIndentation">
<property name="offset" value="2"/>
</module>
<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="allowedAnnotations" value="Override, EventHandler"/>
<property name="tokens" value="METHOD_DEF, ANNOTATION_FIELD_DEF"/> <!-- exclude CTOR_DEF -->
</module>
<module name="MissingJavadocMethod">
<property name="scope" value="package"/>
<property name="minLineCount" value="4"/>
<property name="allowedAnnotations" value="Override, EventHandler"/>
<property name="tokens" value="METHOD_DEF, ANNOTATION_FIELD_DEF"/> <!-- exclude CTOR_DEF -->
</module>
<module name="MissingJavadocMethod">
<property name="scope" value="private"/>
<property name="minLineCount" value="16"/>
<property name="allowedAnnotations" value="Override, EventHandler"/>
<property name="tokens" value="METHOD_DEF, ANNOTATION_FIELD_DEF"/> <!-- exclude CTOR_DEF -->
</module>
<!-- TODO Checkstyle/#4089: need "allowedAnnotations" property to skip @Comment fields
<module name="JavadocVariable">
<property name="scope" value="package"/>
<property name="tokens" value="VARIABLE_DEF"/>
</module>
-->
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="SingleLineJavadoc">
<property name="ignoredTags" value="@return"/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="ignore|ignored"/>
</module>
<module name="MissingOverride"/>
<module name="EqualsHashCode"/>
<module name="EqualsAvoidNull"/>
<module name="Regexp">
<property name="format" value="\.to(Lower|Upper)Case\(\)"/>
<property name="illegalPattern" value="true"/>
<property name="ignoreComments" value="true"/>
<property name="message" value="Use toLowerCase/toUpperCase with Locale"/>
</module>
</module>
<module name="FileTabCharacter"/>
</module>

44
.codeclimate.yml Normal file
View File

@ -0,0 +1,44 @@
version: '2' # required to adjust maintainability checks
plugins:
checkstyle:
enabled: true
config:
file: '.checkstyle.xml'
checks:
# We disable all the following CodeClimate checks: Checkstyle already checks for these things and has the advantage
# that the Checkstyle config can also be used in one's IDE.
argument-count:
enabled: false
complex-logic:
enabled: false
file-lines:
enabled: false
method-complexity:
enabled: false
method-count:
enabled: false
method-lines:
enabled: false
nested-control-flow:
enabled: false
return-statements:
enabled: false
similar-code:
enabled: false
# The "identical-code" check would be cool to enable since Checkstyle offers no such functionality, but it is
# too aggressive and we'd have to suppress many reported warnings.
identical-code:
enabled: false
exclude_patterns:
# 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/PhpBB.java'
- 'src/main/java/fr/xephi/authme/security/crypts/Whirlpool.java'
- 'src/main/java/fr/xephi/authme/security/crypts/Wordpress.java'
# Don't check test classes
- 'src/test/java/**/*Test.java'

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# Top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Set the charset, and space indention
[*.java]
charset = utf-8
indent_style = space
indent_size = 4

25
.github/ISSUE_TEMPLATE.MD vendored Normal file
View File

@ -0,0 +1,25 @@
#### Before reporting an issue make sure you are running the latest build of the plugin and checked for duplicate issues!
### What behaviour is observed:
What happened?
### What behaviour is expected:
What did you expect?
### Steps/models to reproduce:
The actions that cause the issue
### Plugin list:
This can be found by running `/pl`
### Environment description
Standalone server/Bungeecord network, SQLite/MySql, ...
### AuthMe build number:
This can be found by running `/authme version`
### Error Log:
Pastebin/Hastebin/Gist link of the error log or stacktrace (if any)
### Configuration:
Pastebin/Hastebin/Gist link of your config.yml file (remember to delete any sensitive data)

83
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,83 @@
name: Bug report
description: Create a report to help us improve
labels: 'Type: bug'
body:
- type: markdown
attributes:
value: |
Before reporting an issue make sure you are running the latest build of the plugin and checked for duplicate issues!
- type: textarea
attributes:
label: What behaviour is observed?
description: A clear and concise description of what the behavior is.
validations:
required: true
- type: textarea
attributes:
label: Expected behaviour
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: To Reproduce
description: Steps to reproduce this behaviour
placeholder: |
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
validations:
required: true
- type: textarea
attributes:
label: Plugin list
description: This can be found by running `/pl`
validations:
required: true
- type: dropdown
attributes:
label: Server Implementation
description: Which server implementation are you using?
multiple: false
options:
- Standalone server (no proxy)
- BungeeCord
validations:
required: true
- type: dropdown
attributes:
label: Database Implementation
description: Which database implementation are you using?
multiple: false
options:
- SQLite
- MySQL
validations:
required: true
- type: input
attributes:
label: AuthMe Version
description: What version of AuthMe are you running? (`/authme version`)
validations:
required: true
- type: input
attributes:
label: Error log (if applicable)
description: If you are reporting a console error, upload any relevant log excerpts to either https://paste.gg/ or https://gist.github.com, save and the paste the link in this box.
- type: input
attributes:
label: Configuration
description: Link of your config.yml file (remember to delete any sensitive data), upload any relevant log excerpts to either https://paste.gg/ or https://gist.github.com, save and the paste the link in this box.
validations:
required: true

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1 @@
blank_issues_enabled: false

View File

@ -0,0 +1,28 @@
name: Feature request
description: Suggest an idea for AuthMe
labels: 'Type: enhancement'
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request for AuthMe! Fill out the following form to your best ability to help us understand your feature request and greately improve the change of it getting added.
- type: textarea
attributes:
label: What feature do you want to see added?
description: A clear and concise description of your feature request.
validations:
required: true
- type: textarea
attributes:
label: Are there any alternatives?
description: List any alternatives you might have tried
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: You can provide additional context below.

12
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: maven
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
ignore:
- dependency-name: com.google.code.gson:gson
- dependency-name: com.google.guava:guava
- dependency-name: org.apache.logging.log4j:log4j-core
- dependency-name: com.zaxxer:HikariCP

23
.github/workflows/maven.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: Java CI
on:
push:
pull_request:
branches:
- master
jobs:
Build:
strategy:
matrix:
jdkversion: [11]
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: ${{ matrix.jdkversion }}
cache: 'maven'
- name: 使用Maven构建
run: mvn -V -B clean package --file pom.xml

115
.gitignore vendored Normal file
View File

@ -0,0 +1,115 @@
### Java files ###
*.class
MANIFEST.MF
# Package Files
#*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# Mac OS
.DS_Store
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
# Ignore project files
*.iml
*.java___jb_tmp___
# Ignore IDEA directory
.idea/*
# Include the project's code style settings file
!.idea/codeStyleSettings.xml
# File-based project format:
*.ipr
*.iws
### Plugin-specific files: ###
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Eclipse ###
*.pydevproject
.metadata
.gradle
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
# Eclipse Core
.project
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# JDT-specific (Eclipse Java Development Tools)
.classpath
# PDT-specific
.buildpath
# sbteclipse plugin
.target
# TeXlipse plugin
.texlipse
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
nbactions.xml
nb-configuration.xml
.nb-gradle/
### Git ###
# Don't exclude the .gitignore itself
!.gitignore

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# 默认忽略的文件
/shelf/
/workspace.xml

291
.idea/codeStyleSettings.xml generated Normal file
View File

@ -0,0 +1,291 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<option name="AUTODETECT_INDENTS" value="false" />
<option name="LINE_SEPARATOR" value="&#10;" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="JD_ADD_BLANK_AFTER_PARM_COMMENTS" value="true" />
<option name="JD_ADD_BLANK_AFTER_RETURN" value="true" />
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<codeStyleSettings language="JAVA">
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PUBLIC>true</PUBLIC>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PROTECTED>true</PROTECTED>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PRIVATE>true</PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PUBLIC>true</PUBLIC>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PROTECTED>true</PROTECTED>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PRIVATE>true</PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<INITIALIZER_BLOCK>true</INITIALIZER_BLOCK>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PUBLIC>true</PUBLIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PROTECTED>true</PROTECTED>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PRIVATE>true</PRIVATE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PUBLIC>true</PUBLIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PROTECTED>true</PROTECTED>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PRIVATE>true</PRIVATE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<FIELD>true</FIELD>
</match>
</rule>
</section>
<section>
<rule>
<match>
<INITIALIZER_BLOCK>true</INITIALIZER_BLOCK>
</match>
</rule>
</section>
<section>
<rule>
<match>
<CONSTRUCTOR>true</CONSTRUCTOR>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<METHOD>true</METHOD>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<METHOD>true</METHOD>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<METHOD>true</METHOD>
<PRIVATE>true</PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<ENUM>true</ENUM>
</match>
</rule>
</section>
<section>
<rule>
<match>
<INTERFACE>true</INTERFACE>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<CLASS>true</CLASS>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<CLASS>true</CLASS>
</match>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</value>
</option>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</component>
</project>

35
.travis.yml Normal file
View File

@ -0,0 +1,35 @@
dist: focal
language: java
matrix:
include:
- env:
- JDK_VERSION=8
- env:
- JDK_VERSION=11
- env:
- JDK_VERSION=17
before_install:
- "[[ -d $HOME/.sdkman/ ]] && [[ -d $HOME/.sdkman/bin/ ]] || rm -rf $HOME/.sdkman/"
- curl -s "https://get.sdkman.io" | bash
- mkdir -p "$HOME/.sdkman/etc/"
- echo sdkman_auto_answer=true > "$HOME/.sdkman/etc/config"
- echo sdkman_auto_selfupdate=true >> "$HOME/.sdkman/etc/config"
- source "$HOME/.sdkman/bin/sdkman-init.sh"
install:
- sdk install java $(sdk list java | grep -o "$JDK_VERSION\.[0-9]*\.[0-9]*\-open" | head -1)
- sdk install maven
- export JAVA_HOME="$HOME/.sdkman/candidates/java/current"
- export PATH=${JAVA_HOME}/bin:${PATH}
- export MAVEN_HOME="$HOME/.sdkman/candidates/maven/current"
- export M2_HOME="$MAVEN_HOME"
- export PATH=${M2_HOME}/bin:${PATH}
- env
- mvn -v
cache:
directories:
- $HOME/.m2/repository

674
LICENSE Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# AuthMeReReloaded
**"A fork of the best authentication plugin for the Bukkit modding API!"**
![Graph](https://bstats.org/signatures/bukkit/AuthMeReloaded-Fork.svg)

111
docs/commands.md Normal file
View File

@ -0,0 +1,111 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Sun Apr 04 21:31:42 CEST 2021. See docs/commands/commands.tpl.md -->
## AuthMe Commands
You can use the following commands to use the features of AuthMe. Mandatory arguments are marked with `< >`
brackets; optional arguments are enclosed in square brackets (`[ ]`).
- **/authme**: The main AuthMeReloaded command. The root for all admin commands.
- **/authme register** &lt;player> &lt;password>: Register the specified player with the specified password.
<br />Requires `authme.admin.register`
- **/authme unregister** &lt;player>: Unregister the specified player.
<br />Requires `authme.admin.unregister`
- **/authme forcelogin** [player]: Enforce the specified player to login.
<br />Requires `authme.admin.forcelogin`
- **/authme password** &lt;player> &lt;pwd>: Change the password of a player.
<br />Requires `authme.admin.changepassword`
- **/authme lastlogin** [player]: View the date of the specified players last login.
<br />Requires `authme.admin.lastlogin`
- **/authme accounts** [player]: Display all accounts of a player by his player name or IP.
<br />Requires `authme.admin.accounts`
- **/authme email** [player]: Display the email address of the specified player if set.
<br />Requires `authme.admin.getemail`
- **/authme setemail** &lt;player> &lt;email>: Change the email address of the specified player.
<br />Requires `authme.admin.changemail`
- **/authme getip** &lt;player>: Get the IP address of the specified online player.
<br />Requires `authme.admin.getip`
- **/authme totp** &lt;player>: Returns whether the specified player has enabled two-factor authentication.
<br />Requires `authme.admin.totpviewstatus`
- **/authme disabletotp** &lt;player>: Disable two-factor authentication for a player.
<br />Requires `authme.admin.totpdisable`
- **/authme spawn**: Teleport to the spawn.
<br />Requires `authme.admin.spawn`
- **/authme setspawn**: Change the player's spawn to your current position.
<br />Requires `authme.admin.setspawn`
- **/authme firstspawn**: Teleport to the first spawn.
<br />Requires `authme.admin.firstspawn`
- **/authme setfirstspawn**: Change the first player's spawn to your current position.
<br />Requires `authme.admin.setfirstspawn`
- **/authme purge** &lt;days>: Purge old AuthMeReloaded data longer than the specified number of days ago.
<br />Requires `authme.admin.purge`
- **/authme purgeplayer** &lt;player> [options]: Purges data of the given player.
<br />Requires `authme.admin.purgeplayer`
- **/authme backup**: Creates a backup of the registered users.
<br />Requires `authme.admin.backup`
- **/authme resetpos** &lt;player/*>: Purge the last know position of the specified player or all of them.
<br />Requires `authme.admin.purgelastpos`
- **/authme purgebannedplayers**: Purge all AuthMeReloaded data for banned players.
<br />Requires `authme.admin.purgebannedplayers`
- **/authme switchantibot** [mode]: Switch or toggle the AntiBot mode to the specified state.
<br />Requires `authme.admin.switchantibot`
- **/authme reload**: Reload the AuthMeReloaded plugin.
<br />Requires `authme.admin.reload`
- **/authme version**: Show detailed information about the installed AuthMeReloaded version, the developers, contributors, and license.
- **/authme converter** [job]: Converter command for AuthMeReloaded.
<br />Requires `authme.admin.converter`
- **/authme messages**: Adds missing texts to the current help messages file.
<br />Requires `authme.admin.updatemessages`
- **/authme recent**: Shows the last players that have logged in.
<br />Requires `authme.admin.seerecent`
- **/authme debug** [child] [arg] [arg]: Allows various operations for debugging.
<br />Requires `authme.debug.command`
- **/authme help** [query]: View detailed help for /authme commands.
- **/email**: The AuthMeReloaded email command base.
- **/email show**: Show your current email address.
<br />Requires `authme.player.email.see`
- **/email add** &lt;email> &lt;verifyEmail>: Add a new email address to your account.
<br />Requires `authme.player.email.add`
- **/email change** &lt;oldEmail> &lt;newEmail>: Change an email address of your account.
<br />Requires `authme.player.email.change`
- **/email recover** &lt;email>: Recover your account using an Email address by sending a mail containing a new password.
<br />Requires `authme.player.email.recover`
- **/email code** &lt;code>: Recover your account by submitting a code delivered to your email.
<br />Requires `authme.player.email.recover`
- **/email setpassword** &lt;password>: Set a new password after successfully recovering your account.
<br />Requires `authme.player.email.recover`
- **/email help** [query]: View detailed help for /email commands.
- **/login** &lt;password>: Command to log in using AuthMeReloaded.
<br />Requires `authme.player.login`
- **/login help** [query]: View detailed help for /login commands.
- **/logout**: Command to logout using AuthMeReloaded.
<br />Requires `authme.player.logout`
- **/logout help** [query]: View detailed help for /logout commands.
- **/register** [password] [verifyPassword]: Command to register using AuthMeReloaded.
<br />Requires `authme.player.register`
- **/register help** [query]: View detailed help for /register commands.
- **/unregister** &lt;password>: Command to unregister using AuthMeReloaded.
<br />Requires `authme.player.unregister`
- **/unregister help** [query]: View detailed help for /unregister commands.
- **/changepassword** &lt;oldPassword> &lt;newPassword>: Command to change your password using AuthMeReloaded.
<br />Requires `authme.player.changepassword`
- **/changepassword help** [query]: View detailed help for /changepassword commands.
- **/totp**: Performs actions related to two-factor authentication.
- **/totp code** &lt;code>: Processes the two-factor authentication code during login.
- **/totp add**: Enables two-factor authentication for your account.
<br />Requires `authme.player.totpadd`
- **/totp confirm** &lt;code>: Saves the generated TOTP secret after confirmation.
<br />Requires `authme.player.totpadd`
- **/totp remove** &lt;code>: Disables two-factor authentication for your account.
<br />Requires `authme.player.totpremove`
- **/totp help** [query]: View detailed help for /totp commands.
- **/captcha** &lt;captcha>: Captcha command for AuthMeReloaded.
<br />Requires `authme.player.captcha`
- **/captcha help** [query]: View detailed help for /captcha commands.
- **/verification** &lt;code>: Command to complete the verification process for AuthMeReloaded.
<br />Requires `authme.player.security.verificationcode`
- **/verification help** [query]: View detailed help for /verification commands.
---
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Apr 04 21:31:42 CEST 2021

584
docs/config.md Normal file
View File

@ -0,0 +1,584 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Thu Jul 28 18:11:22 CEST 2022. 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,
with which you can configure various settings. The following is the initial contents of
the generated config.yml file.
```yml
DataSource:
# What type of database do you want to use?
# Valid values: SQLITE, MARIADB, MYSQL, POSTGRESQL
backend: SQLITE
# Enable the database caching system, should be disabled on bungeecord environments
# or when a website integration is being used.
caching: true
# Database host address
mySQLHost: 127.0.0.1
# Database port
mySQLPort: '3306'
# Connect to MySQL database over SSL
mySQLUseSSL: true
# Verification of server's certificate.
# We would not recommend to set this option to false.
# Set this option to false at your own risk if and only if you know what you're doing
mySQLCheckServerCertificate: true
# Authorize client to retrieve RSA server public key.
# Advanced option, ignore if you don't know what it means.
mySQLAllowPublicKeyRetrieval: true
# Username to connect to the MySQL database
mySQLUsername: authme
# Password to connect to the MySQL database
mySQLPassword: '12345'
# Database Name, use with converters or as SQLITE database name
mySQLDatabase: authme
# Table of the database
mySQLTablename: authme
# Column of IDs to sort data
mySQLColumnId: id
# Column for storing or checking players nickname
mySQLColumnName: username
# Column for storing or checking players RealName
mySQLRealName: realname
# Column for storing players passwords
mySQLColumnPassword: password
# Column for storing players passwords salts
mySQLColumnSalt: ''
# Column for storing players emails
mySQLColumnEmail: email
# Column for storing if a player is logged in or not
mySQLColumnLogged: isLogged
# Column for storing if a player has a valid session or not
mySQLColumnHasSession: hasSession
# Column for storing a player's TOTP key (for two-factor authentication)
mySQLtotpKey: totp
# Column for storing the player's last IP
mySQLColumnIp: ip
# Column for storing players lastlogins
mySQLColumnLastLogin: lastlogin
# Column storing the registration date
mySQLColumnRegisterDate: regdate
# Column for storing the IP address at the time of registration
mySQLColumnRegisterIp: regip
# Column for storing player LastLocation - X
mySQLlastlocX: x
# Column for storing player LastLocation - Y
mySQLlastlocY: y
# Column for storing player LastLocation - Z
mySQLlastlocZ: z
# Column for storing player LastLocation - World Name
mySQLlastlocWorld: world
# Column for storing player LastLocation - Yaw
mySQLlastlocYaw: yaw
# Column for storing player LastLocation - Pitch
mySQLlastlocPitch: pitch
# Column for storing players uuids (optional)
mySQLPlayerUUID: ''
# Overrides the size of the DB Connection Pool, default = 10
poolSize: 10
# The maximum lifetime of a connection in the pool, default = 1800 seconds
# You should set this at least 30 seconds less than mysql server wait_timeout
maxLifetime: 1800
ExternalBoardOptions:
# Column for storing players groups
mySQLColumnGroup: ''
# -1 means disabled. If you want that only activated players
# can log into your server, you can set here the group number
# of unactivated users, needed for some forum/CMS support
nonActivedUserGroup: -1
# Other MySQL columns where we need to put the username (case-sensitive)
mySQLOtherUsernameColumns: []
# How much log2 rounds needed in BCrypt (do not change if you do not know what it does)
bCryptLog2Round: 12
# phpBB table prefix defined during the phpBB installation process
phpbbTablePrefix: phpbb_
# phpBB activated group ID; 2 is the default registered group defined by phpBB
phpbbActivatedGroupId: 2
# IP Board table prefix defined during the IP Board installation process
IPBTablePrefix: ipb_
# IP Board default group ID; 3 is the default registered group defined by IP Board
IPBActivatedGroupId: 3
# Xenforo table prefix defined during the Xenforo installation process
XFTablePrefix: xf_
# XenForo default group ID; 2 is the default registered group defined by Xenforo
XFActivatedGroupId: 2
# Wordpress prefix defined during WordPress installation
wordpressTablePrefix: wp_
settings:
sessions:
# Do you want to enable the session feature?
# If enabled, when a player authenticates successfully,
# his IP and his nickname is saved.
# The next time the player joins the server, if his IP
# is the same as last time and the timeout hasn't
# expired, he will not need to authenticate.
enabled: false
# After how many minutes should a session expire?
# A player's session ends after the timeout or if his IP has changed
timeout: 10
# Message language, available languages:
# https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/translations.md
messagesLanguage: en
# Forces authme to hook into Vault instead of a specific permission handler system.
forceVaultHook: false
# Log level: INFO, FINE, DEBUG. Use INFO for general messages,
# FINE for some additional detailed ones (like password failed),
# and DEBUG for debugging
logLevel: FINE
# By default we schedule async tasks when talking to the database. If you want
# typical communication with the database to happen synchronously, set this to false
useAsyncTasks: true
# The name of the server, used in some placeholders.
serverName: Your Minecraft Server
restrictions:
# Can not authenticated players chat?
# Keep in mind that this feature also blocks all commands not
# listed in the list below.
allowChat: false
# Hide the chat log from players who are not authenticated?
hideChat: false
# Allowed commands for unauthenticated players
allowCommands:
- /login
- /log
- /l
- /register
- /reg
- /email
- /captcha
- /2fa
- /totp
# Max number of allowed registrations per IP
# The value 0 means an unlimited number of registrations!
maxRegPerIp: 1
# Minimum allowed username length
minNicknameLength: 3
# Maximum allowed username length
maxNicknameLength: 16
# When this setting is enabled, online players can't be kicked out
# due to "Logged in from another Location"
# This setting will prevent potential security exploits.
ForceSingleSession: true
ForceSpawnLocOnJoin:
# If enabled, every player that spawn in one of the world listed in
# "ForceSpawnLocOnJoin.worlds" will be teleported to the spawnpoint after successful
# authentication. The quit location of the player will be overwritten.
# This is different from "teleportUnAuthedToSpawn" that teleport player
# to the spawnpoint on join.
enabled: false
# WorldNames where we need to force the spawn location
# Case-sensitive!
worlds:
- world
- world_nether
- world_the_end
# This option will save the quit location of the players.
SaveQuitLocation: false
# To activate the restricted user feature you need
# to enable this option and configure the AllowedRestrictedUser field.
AllowRestrictedUser: false
# The restricted user feature will kick players listed below
# if they don't match the defined IP address. Names are case-insensitive.
# You can use * as wildcard (127.0.0.*), or regex with a "regex:" prefix regex:127\.0\.0\..*
# Example:
# AllowedRestrictedUser:
# - playername;127.0.0.1
# - playername;regex:127\.0\.0\..*
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?
kickOnWrongPassword: true
# Should not logged in players be teleported to the spawn?
# After the authentication they will be teleported back to
# their normal position.
teleportUnAuthedToSpawn: false
# Can unregistered players walk around?
allowMovement: false
# After how many seconds should players who fail to login or register
# be kicked? Set to 0 to disable.
timeout: 30
# 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
allowedMovementRadius: 100
# Should we protect the player inventory before logging in? Requires ProtocolLib.
ProtectInventoryBeforeLogIn: true
# Should we deny the tabcomplete feature before logging in? Requires ProtocolLib.
DenyTabCompleteBeforeLogin: false
# Should we display all other accounts from a player when he joins?
# permission: /authme.admin.accounts
displayOtherAccounts: true
# Spawn priority; values: authme, essentials, cmi, multiverse, default
spawnPriority: authme,essentials,cmi,multiverse,default
# Maximum Login authorized by IP
maxLoginPerIp: 0
# Maximum Join authorized by IP
maxJoinPerIp: 0
# AuthMe will NEVER teleport players if set to true!
noTeleport: false
# Regex syntax for allowed chars in passwords. The default [!-~] allows all visible ASCII
# characters, which is what we recommend. See also http://asciitable.com
# You can test your regex with https://regex101.com
allowedPasswordCharacters: '[!-~]*'
GameMode:
# Force survival gamemode when player joins?
ForceSurvivalMode: false
unrestrictions:
# Below you can list all account names that AuthMe will ignore
# for registration or login. Configure it at your own risk!!
# This option adds compatibility with BuildCraft and some other mods.
# It is case-insensitive! Example:
# UnrestrictedName:
# - 'npcPlayer'
# - 'npcPlayer2'
UnrestrictedName: []
# Below you can list all inventories names that AuthMe will ignore
# for registration or login. Configure it at your own risk!!
# This option adds compatibility with some mods.
# It is case-insensitive! Example:
# UnrestrictedInventories:
# - 'myCustomInventory1'
# - 'myCustomInventory2'
UnrestrictedInventories: []
security:
# Minimum length of password
minPasswordLength: 5
# Maximum length of password
passwordMaxLength: 30
# Possible values: SHA256, BCRYPT, BCRYPT2Y, PBKDF2, SALTEDSHA512,
# MYBB, IPB3, PHPBB, PHPFUSION, SMF, XENFORO, XAUTH, JOOMLA, WBB3, WBB4, MD5VB,
# PBKDF2DJANGO, WORDPRESS, ROYALAUTH, ARGON2, CUSTOM (for developers only). See full list at
# https://github.com/AuthMe/AuthMeReloaded/blob/master/docs/hash_algorithms.md
# If you use ARGON2, check that you have the argon2 c library on your system
passwordHash: SHA256
# If a password check fails, AuthMe will also try to check with the following hash methods.
# Use this setting when you change from one hash method to another.
# AuthMe will update the password to the new hash. Example:
# legacyHashes:
# - 'SHA1'
legacyHashes: []
# Salt length for the SALTED2MD5 MD5(MD5(password)+salt)
doubleMD5SaltLength: 8
# Number of rounds to use if passwordHash is set to PBKDF2. Default is 10000
pbkdf2Rounds: 10000
# Prevent unsafe passwords from being used; put them in lowercase!
# You should always set 'help' as unsafePassword due to possible conflicts.
# unsafePasswords:
# - '123456'
# - 'password'
# - 'help'
unsafePasswords:
- '123456'
- password
- qwerty
- '12345'
- '54321'
- '123456789'
- help
registration:
# Enable registration on the server?
enabled: true
# Send every X seconds a message to a player to
# remind him that he has to login/register
messageInterval: 5
# Only registered and logged in players can play.
# See restrictions for exceptions
force: true
# Type of registration: PASSWORD or EMAIL
# PASSWORD = account is registered with a password supplied by the user;
# EMAIL = password is generated and sent to the email provided by the user.
# More info at https://github.com/AuthMe/AuthMeReloaded/wiki/Registration
type: PASSWORD
# Second argument the /register command should take: NONE = no 2nd argument
# CONFIRMATION = must repeat first argument (pass or email)
# EMAIL_OPTIONAL = for password register: 2nd argument can be empty or have email address
# EMAIL_MANDATORY = for password register: 2nd argument MUST be an email address
secondArg: CONFIRMATION
# Do we force kick a player after a successful registration?
# Do not use with login feature below
forceKickAfterRegister: false
# Does AuthMe need to enforce a /login after a successful registration?
forceLoginAfterRegister: false
# Broadcast the welcome message to the server or only to the player?
# set true for server or false for player
broadcastWelcomeMessage: false
# Should we delay the join message and display it once the player has logged in?
delayJoinMessage: false
# The custom join message that will be sent after a successful login,
# keep empty to use the original one.
# Available variables:
# {PLAYERNAME}: the player name (no colors)
# {DISPLAYNAME}: the player display name (with colors)
# {DISPLAYNAMENOCOLOR}: the player display name (without colors)
customJoinMessage: ''
# Should we remove the leave messages of unlogged users?
removeUnloggedLeaveMessage: false
# Should we remove join messages altogether?
removeJoinMessage: false
# Should we remove leave messages altogether?
removeLeaveMessage: false
# Do we need to add potion effect Blinding before login/register?
applyBlindEffect: false
# 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
GroupOptions:
# Enables switching a player to defined permission groups before they log in.
# See below for a detailed explanation.
enablePermissionCheck: false
# This is a very important option: if a registered player joins the server
# AuthMe will switch him to unLoggedInGroup. This should prevent all major exploits.
# You can set up your permission plugin with this special group to have no permissions,
# or only permission to chat (or permission to send private messages etc.).
# The better way is to set up this group with few permissions, so if a player
# tries to exploit an account they can do only what you've defined for the group.
# After login, the player will be moved to his correct permissions group!
# Please note that the group name is case-sensitive, so 'admin' is different from 'Admin'
# Otherwise your group will be wiped and the player will join in the default group []!
# Example: registeredPlayerGroup: 'NotLogged'
registeredPlayerGroup: ''
# Similar to above, unregistered players can be set to the following
# permissions group
unregisteredPlayerGroup: ''
Email:
# Email SMTP server host
mailSMTP: smtp.gmail.com
# Email SMTP server port
mailPort: 465
# Only affects port 25: enable TLS/STARTTLS?
useTls: true
# Email account which sends the mails
mailAccount: ''
# Email account password
mailPassword: ''
# Email address, fill when mailAccount is not the email address of the account
mailAddress: ''
# Custom sender name, replacing the mailAccount name in the email
mailSenderName: ''
# Recovery password length
RecoveryPasswordLength: 8
# Mail Subject
mailSubject: Your new AuthMe password
# Like maxRegPerIP but with email
maxRegPerEmail: 1
# Recall players to add an email?
recallPlayers: false
# Delay in minute for the recall scheduler
delayRecall: 5
# Blacklist these domains for emails
emailBlacklisted:
- 10minutemail.com
# Whitelist ONLY these domains for emails
emailWhitelisted: []
# Send the new password drawn in an image?
generateImage: false
# The OAuth2 token
emailOauth2Token: ''
Hooks:
# Do we need to hook with multiverse for spawn checking?
multiverse: true
# Do we need to hook with BungeeCord?
bungeecord: false
# Send player to this BungeeCord server after register/login
sendPlayerTo: ''
# Do we need to disable Essentials SocialSpy on join?
disableSocialSpy: false
# Do we need to force /motd Essentials command on join?
useEssentialsMotd: false
Protection:
# Enable some servers protection (country based login, antibot)
enableProtection: false
# Apply the protection also to registered usernames
enableProtectionRegistered: true
geoIpDatabase:
# The MaxMind clientId used to download the GeoIp database,
# get one at https://www.maxmind.com/en/accounts/current/license-key
# The EssentialsX project has a very useful tutorial on how to generate
# the license key: https://github.com/EssentialsX/Wiki/blob/master/GeoIP.md
clientId: ''
# The MaxMind licenseKey used to download the GeoIp database.
licenseKey: ''
# Countries allowed to join the server and register. For country codes, see
# https://dev.maxmind.com/geoip/legacy/codes/iso3166/
# Use "LOCALHOST" for local addresses.
# PLEASE USE QUOTES!
countries:
- US
- GB
- LOCALHOST
# Countries not allowed to join the server and register
# PLEASE USE QUOTES!
countriesBlacklist:
- A1
# Do we need to enable automatic antibot system?
enableAntiBot: true
# The interval in seconds
antiBotInterval: 5
# Max number of players allowed to login in the interval
# before the AntiBot system is enabled automatically
antiBotSensibility: 10
# Duration in minutes of the antibot automatic system
antiBotDuration: 10
# Delay in seconds before the antibot activation
antiBotDelay: 60
quickCommands:
# Kicks the player that issued a command before the defined time after the join process
denyCommandsBeforeMilliseconds: 1000
Purge:
# If enabled, AuthMe automatically purges old, unused accounts
useAutoPurge: false
# Number of days after which an account should be purged
daysBeforeRemovePlayer: 60
# Do we need to remove the player.dat file during purge process?
removePlayerDat: false
# Do we need to remove the Essentials/userdata/player.yml file during purge process?
removeEssentialsFile: false
# World in which the players.dat are stored
defaultWorld: world
# Remove LimitedCreative/inventories/player.yml, player_creative.yml files during purge?
removeLimitedCreativesInventories: false
# Do we need to remove the AntiXRayData/PlayerData/player file during purge process?
removeAntiXRayFile: false
# Do we need to remove permissions?
removePermissions: false
Security:
SQLProblem:
# Stop the server if we can't contact the sql database
# Take care with this, if you set this to false,
# AuthMe will automatically disable and the server won't be protected!
stopServer: true
console:
# Copy AuthMe log output in a separate file as well?
logConsole: true
captcha:
# Enable captcha when a player uses wrong password too many times
useCaptcha: false
# Max allowed tries before a captcha is required
maxLoginTry: 5
# Captcha length
captchaLength: 5
# Minutes after which login attempts count is reset for a player
captchaCountReset: 60
# Require captcha before a player may register?
requireForRegistration: false
tempban:
# Tempban a user's IP address if they enter the wrong password too many times
enableTempban: false
# How many times a user can attempt to login before their IP being tempbanned
maxLoginTries: 10
# The length of time a IP address will be tempbanned in minutes
# Default: 480 minutes, or 8 hours
tempbanLength: 480
# How many minutes before resetting the count for failed logins by IP and username
# Default: 480 minutes (8 hours)
minutesBeforeCounterReset: 480
# The command to execute instead of using the internal ban system, empty if disabled.
# Available placeholders: %player%, %ip%
customCommand: ''
recoveryCode:
# Number of characters a recovery code should have (0 to disable)
length: 8
# How many hours is a recovery code valid for?
validForHours: 4
# Max number of tries to enter recovery code
maxTries: 3
# How long a player has after password recovery to change their password
# without logging in. This is in minutes.
# Default: 2 minutes
passwordChangeTimeout: 2
emailRecovery:
# Seconds a user has to wait for before a password recovery mail may be sent again
# This prevents an attacker from abusing AuthMe's email feature.
cooldown: 60
privacy:
# The mail shown using /email show will be partially hidden
# E.g. (if enabled)
# original email: my.email@example.com
# hidden email: my.***@***mple.com
enableEmailMasking: false
# Minutes after which a verification code will expire
verificationCodeExpiration: 10
# Before a user logs in, various properties are temporarily removed from the player,
# such as OP status, ability to fly, and walk/fly speed.
# Once the user is logged in, we add back the properties we previously saved.
# In this section, you may define how these properties should be handled.
# Read more at https://github.com/AuthMe/AuthMeReloaded/wiki/Limbo-players
limbo:
persistence:
# Besides storing the data in memory, you can define if/how the data should be persisted
# on disk. This is useful in case of a server crash, so next time the server starts we can
# properly restore things like OP status, ability to fly, and walk/fly speed.
# DISABLED: no disk storage,
# INDIVIDUAL_FILES: each player data in its own file,
# DISTRIBUTED_FILES: distributes players into different files based on their UUID, see below
type: INDIVIDUAL_FILES
# This setting only affects DISTRIBUTED_FILES persistence. The distributed file
# persistence attempts to reduce the number of files by distributing players into various
# buckets based on their UUID. This setting defines into how many files the players should
# be distributed. Possible values: ONE, FOUR, EIGHT, SIXTEEN, THIRTY_TWO, SIXTY_FOUR,
# ONE_TWENTY for 128, TWO_FIFTY for 256.
# For example, if you expect 100 non-logged in players, setting to SIXTEEN will average
# 6.25 players per file (100 / 16).
# Note: if you change this setting all data will be migrated. If you have a lot of data,
# change this setting only on server restart, not with /authme reload.
distributionSize: SIXTEEN
# Whether the player is allowed to fly: RESTORE, ENABLE, DISABLE, NOTHING.
# RESTORE sets back the old property from the player. NOTHING will prevent AuthMe
# from modifying the 'allow flight' property on the player.
restoreAllowFlight: RESTORE
# Restore fly speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO.
# RESTORE: restore the speed the player had;
# DEFAULT: always set to default speed;
# MAX_RESTORE: take the maximum of the player's current speed and the previous one
# RESTORE_NO_ZERO: Like 'restore' but sets speed to default if the player's speed was 0
restoreFlySpeed: RESTORE_NO_ZERO
# Restore walk speed: RESTORE, DEFAULT, MAX_RESTORE, RESTORE_NO_ZERO.
# See above for a description of the values.
restoreWalkSpeed: RESTORE_NO_ZERO
BackupSystem:
# General configuration for backups: if false, no backups are possible
ActivateBackup: false
# Create backup at every start of server
OnServerStart: false
# Create backup at every stop of server
OnServerStop: true
# Windows only: MySQL installation path
MysqlWindowsPath: C:\Program Files\MySQL\MySQL Server 5.1\
# Converter settings: see https://github.com/AuthMe/AuthMeReloaded/wiki/Converters
Converter:
Rakamak:
# Rakamak file name
fileName: users.rak
# Rakamak use IP?
useIP: false
# Rakamak IP file name
ipFileName: UsersIp.rak
CrazyLogin:
# CrazyLogin database file name
fileName: accounts.db
loginSecurity:
# LoginSecurity: convert from SQLite; if false we use MySQL
useSqlite: true
mySql:
# LoginSecurity MySQL: database host
host: ''
# LoginSecurity MySQL: database name
database: ''
# LoginSecurity MySQL: database user
user: ''
# LoginSecurity MySQL: password for database user
password: ''
```
To change settings on a running server, save your changes to config.yml and use
`/authme reload`.
---
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Thu Jul 28 18:11:22 CEST 2022

83
docs/hash_algorithms.md Normal file
View File

@ -0,0 +1,83 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Sun Apr 04 21:31:44 CEST 2021. See docs/hashmethods/hash_algorithms.tpl.md -->
## Hash Algorithms
AuthMe supports the following hash algorithms for storing your passwords safely.
Algorithm | Recommendation | Hash length | ASCII | | Salt type | Length | Separate?
--------- | -------------- | ----------- | ----- | --- | --------- | ------ | ---------
ARGON2 | Recommended | 96 | | | Text | 16 |
BCRYPT | Recommended | 60 | | | Text | 22 |
BCRYPT2Y | Recommended | 60 | | | Text | 22 |
CMW | Do not use | 32 | | | None | |
CRAZYCRYPT1 | Do not use | 128 | | | Username | |
IPB3 | Acceptable | 32 | | | Text | 5 | Y
IPB4 | Does not work | 60 | | | Text | 22 | Y
JOOMLA | Acceptable | 65 | | | Text | 32 |
MD5VB | Acceptable | 56 | | | Text | 16 |
MYBB | Acceptable | 32 | | | Text | 8 | Y
PBKDF2 | Recommended | 165 | | | Text | 16 |
PBKDF2DJANGO | Acceptable | 77 | Y | | Text | 12 |
PHPBB | Acceptable | 60 | | | Text | 22 |
PHPFUSION | Do not use | 64 | Y | | | | Y
ROYALAUTH | Do not use | 128 | | | None | |
SALTED2MD5 | Acceptable | 32 | | | Text | | Y
SALTEDSHA512 | Recommended | 128 | | | | | Y
SHA256 | Recommended | 86 | | | Text | 16 |
SMF | Do not use | 40 | | | Username | | Y
TWO_FACTOR | Does not work | 16 | | | None | |
WBB3 | Acceptable | 40 | | | Text | 40 | Y
WBB4 | Recommended | 60 | | | Text | 22 |
WORDPRESS | Acceptable | 34 | | | Text | 9 |
XAUTH | Recommended | 140 | | | Text | 12 |
XFBCRYPT | Recommended | 60 | | | Text | 22 |
CUSTOM | | | | | | | |
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
### Columns
#### Algorithm
The algorithm is the hashing algorithm used to store passwords with. Default is SHA256 and is recommended.
You can change the hashing algorithm in the config.yml: under `security`, locate `passwordHash`.
#### Recommendation
The recommendation lists our usage recommendation in terms of how secure it is (not how _well_ the algorithm works!).
- Recommended: The hash algorithm appears to be cryptographically secure and is one we recommend.
- Acceptable: There are safer algorithms that can be chosen but using the algorithm is generally OK.
- Deprecated: Cannot be used anymore actively or in the near future.
- Do not use: Hash algorithm isn't sufficiently secure. Use only if required to hook into another system.
- Does not work: The algorithm does not work properly; do not use.
#### Hash Length
The length of the hashes the algorithm produces. Note that the hash length is not (primarily) indicative of
whether an algorithm is secure or not.
#### ASCII
If denoted with a **y**, means that the algorithm is restricted to ASCII characters only, i.e. it will simply ignore
"special characters" such as `ÿ` or `Â`. Note that we do not recommend the use of "special characters" in passwords.
#### Salt Columns
Before hashing, a _salt_ may be appended to the password to make the hash more secure. The following columns describe
the salt the algorithm uses.
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
##### Salt Type
We do not recommend the usage
of any algorithm that doesn't use a randomly generated text as salt. This "salt type" column indicates what type of
salt the algorithm uses:
- Text: randomly generated text (see also the following column, "Length")
- Username: the salt is constructed from the username (bad)
- None: the algorithm uses no salt (bad)
##### Length
If applicable (salt type is "Text"), indicates the length of the generated salt. The longer the better.
If this column is empty when the salt type is "Text", it typically means the salt length can be defined in config.yml.
##### Separate
If denoted with a **y**, it means that the salt is stored in a separate column in the database. This is neither good
or bad.
---
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Apr 04 21:31:44 CEST 2021

76
docs/permission_nodes.md Normal file
View File

@ -0,0 +1,76 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Sun Apr 04 21:31:44 CEST 2021. See docs/permissions/permission_nodes.tpl.md -->
## AuthMe Permission Nodes
The following are the permission nodes that are currently supported by the latest dev builds.
- **authme.admin.*** Give access to all admin commands.
- **authme.admin.accounts** Administrator command to see all accounts associated with a user.
- **authme.admin.antibotmessages** Permission to see Antibot messages.
- **authme.admin.backup** Allows to use the backup command.
- **authme.admin.changemail** Administrator command to set or change the email address of a user.
- **authme.admin.changepassword** Administrator command to change the password of a user.
- **authme.admin.converter** Administrator command to convert old or other data to AuthMe data.
- **authme.admin.firstspawn** Administrator command to teleport to the first AuthMe spawn.
- **authme.admin.forcelogin** Administrator command to force-login an existing user.
- **authme.admin.getemail** Administrator command to get the email address of a user, if set.
- **authme.admin.getip** Administrator command to get the last known IP of a user.
- **authme.admin.lastlogin** Administrator command to see the last login date and time of a user.
- **authme.admin.purge** Administrator command to purge old user data.
- **authme.admin.purgebannedplayers** Administrator command to purge all data associated with banned players.
- **authme.admin.purgelastpos** Administrator command to purge the last position of a user.
- **authme.admin.purgeplayer** Administrator command to purge a given player.
- **authme.admin.register** Administrator command to register a new user.
- **authme.admin.reload** Administrator command to reload the plugin configuration.
- **authme.admin.seeotheraccounts** Permission to see the other accounts of the players that log in.
- **authme.admin.seerecent** Administrator command to see the last recently logged in players.
- **authme.admin.setfirstspawn** Administrator command to set the first AuthMe spawn.
- **authme.admin.setspawn** Administrator command to set the AuthMe spawn.
- **authme.admin.spawn** Administrator command to teleport to the AuthMe spawn.
- **authme.admin.switchantibot** Administrator command to toggle the AntiBot protection status.
- **authme.admin.totpdisable** Administrator command to disable the two-factor auth of a user.
- **authme.admin.totpviewstatus** Administrator command to see whether a player has enabled two-factor authentication.
- **authme.admin.unregister** Administrator command to unregister an existing user.
- **authme.admin.updatemessages** Permission to use the update messages command.
- **authme.allowchatbeforelogin** Permission to send chat messages before being logged in.
- **authme.allowmultipleaccounts** Permission to be able to register multiple accounts.
- **authme.bypassantibot** Permission node to bypass AntiBot protection.
- **authme.bypassbungeesend** Permission node to bypass BungeeCord server teleportation.
- **authme.bypasscountrycheck** Permission to bypass the GeoIp country code check.
- **authme.bypassforcesurvival** Permission for users to bypass force-survival mode.
- **authme.bypasspurge** Permission to bypass the purging process.
- **authme.debug.command** General permission to use the /authme debug command.
- **authme.debug.country** Permission to use the country lookup section.
- **authme.debug.db** Permission to view data from the database.
- **authme.debug.group** Permission to view permission groups.
- **authme.debug.limbo** Permission to use the limbo data viewer.
- **authme.debug.mail** Permission to use the test email sender.
- **authme.debug.mysqldef** Permission to change nullable status of MySQL columns.
- **authme.debug.perm** Permission to use the permission checker.
- **authme.debug.spawn** Permission to view spawn information.
- **authme.debug.stats** Permission to use the stats section.
- **authme.debug.valid** Permission to use sample validation.
- **authme.player.*** Permission to use all player (non-admin) commands.
- **authme.player.canbeforced** Permission for users a login can be forced to.
- **authme.player.captcha** Command permission to use captcha.
- **authme.player.changepassword** Command permission to change the password.
- **authme.player.email** Grants all email permissions.
- **authme.player.email.add** Command permission to add an email address.
- **authme.player.email.change** Command permission to change the email address.
- **authme.player.email.recover** Command permission to recover an account using its email address.
- **authme.player.email.see** Command permission to see the own email address.
- **authme.player.login** Command permission to login.
- **authme.player.logout** Command permission to logout.
- **authme.player.protection.quickcommandsprotection** Permission that enables on join quick commands checks for the player.
- **authme.player.register** Command permission to register.
- **authme.player.security.verificationcode** Permission to use the email verification codes feature.
- **authme.player.seeownaccounts** Permission to use to see own other accounts.
- **authme.player.totpadd** Permission to enable two-factor authentication.
- **authme.player.totpremove** Permission to disable two-factor authentication.
- **authme.player.unregister** Command permission to unregister.
- **authme.vip** When the server is full and someone with this permission joins the server, someone will be kicked.
---
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Apr 04 21:31:44 CEST 2021

46
docs/translations.md Normal file
View File

@ -0,0 +1,46 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Sun Apr 04 21:31:44 CEST 2021. See docs/translations/translations.tpl.md -->
# AuthMe Translations
The following translations are available in AuthMe. Set `messagesLanguage` to the language code
in your config.yml to use the language, or use another language code to start a new translation.
Code | Language | Translated | &nbsp;
---- | -------- | ---------: | ------
[en](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_en.yml) | English | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 99% | <img src="https://via.placeholder.com/99x7/66ee55?text=%20" alt="99" />
[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 99% | <img src="https://via.placeholder.com/99x7/66ee55?text=%20" alt="99" />
[eo](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eo.yml) | Esperanto | 79% | <img src="https://via.placeholder.com/79x7/bb9900?text=%20" alt="79" />
[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 99% | <img src="https://via.placeholder.com/99x7/66ee55?text=%20" alt="99" />
[et](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_et.yml) | Estonian | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 45% | <img src="https://via.placeholder.com/45x7/aa5500?text=%20" alt="45" />
[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 48% | <img src="https://via.placeholder.com/48x7/aa5500?text=%20" alt="48" />
[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 99% | <img src="https://via.placeholder.com/99x7/66ee55?text=%20" alt="99" />
[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 93% | <img src="https://via.placeholder.com/93x7/77dd44?text=%20" alt="93" />
[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 99% | <img src="https://via.placeholder.com/99x7/66ee55?text=%20" alt="99" />
[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 36% | <img src="https://via.placeholder.com/36x7/aa4400?text=%20" alt="36" />
[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 99% | <img src="https://via.placeholder.com/99x7/66ee55?text=%20" alt="99" />
[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[si](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_si.yml) | Slovenian | 99% | <img src="https://via.placeholder.com/99x7/66ee55?text=%20" alt="99" />
[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 79% | <img src="https://via.placeholder.com/79x7/bb9900?text=%20" alt="79" />
[sr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sr.yml) | Serbian | 99% | <img src="https://via.placeholder.com/99x7/66ee55?text=%20" alt="99" />
[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 77% | <img src="https://via.placeholder.com/77x7/bb9900?text=%20" alt="77" />
[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 100% | <img src="https://via.placeholder.com/100x7/66ff66?text=%20" alt="100" />
[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 99% | <img src="https://via.placeholder.com/99x7/66ee55?text=%20" alt="99" />
[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 64% | <img src="https://via.placeholder.com/64x7/bb7700?text=%20" alt="64" />
[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 86% | <img src="https://via.placeholder.com/86x7/99bb22?text=%20" alt="86" />
---
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Apr 04 21:31:44 CEST 2021

1082
pom.xml Normal file

File diff suppressed because it is too large Load Diff

564
samples/NewConfig.yml Normal file
View File

@ -0,0 +1,564 @@
# =======================================================================================================
# _____ __ .__ _____ __________ .__ .___ .___
# / _ \ __ ___/ |_| |__ / \ ____\______ \ ____ | | _________ __| _/____ __| _/
# / /_\ \| | \ __| | \ / \ / \_/ __ \| __/ __ \| | / _ \__ \ / __ _/ __ \ / __ |
# / | | | /| | | Y / Y \ ___/| | \ ___/| |_( <_> / __ \/ /_/ \ ___// /_/ |
# \____|__ |____/ |__| |___| \____|__ /\___ |____|_ /\___ |____/\____(____ \____ |\___ \____ |
# \/ \/ \/ \/ \/ \/ \/ \/ \/ \/
#
# =======================================================================================================
#
# Authme Main Configuration File.
#
# =======================================================================================================
# Plugin infos (overwritten on start, just a simple way to find out your plugin version).
authors: ${pluginAuthors}
version: ${project.version}
buildNumber: ${buildNumber}
# Set this setting to true when you have configured the plugin,
# when false the server will be stopped with a warning message.
enabled: false
# Database settings.
data_source:
# ===========================
# Database general settings.
# ===========================
# Database backend (sqlite, mysql).
backend: sqlite
# Enable database queries caching, should improve performance.
caching: true
# ===========================
# SqLite db parameters.
# ===========================
sqlite:
# The name of the database storage file.
filename: 'authme.db'
# ===========================
# MySql db parameters.
# ===========================
mysql:
# Connection parameters.
host: '127.0.0.1'
port: 3306
username: 'change_me'
password: 'change_me'
database: 'my_minecraft_server'
tablename: 'authme'
# Column names.
column_names:
id: id
# Column for storing nicknames (ignore case nickname).
name: username
# Column for storing the realname (case sensitive nickname).
real_name: realname
# Column for storing passwords.
password: password
# Column for storing email addresses.
email: email
# Column for storing the authentication status (logged or not).
login_status: isLogged
# Column for storing player IPs.
ip: ip
# Column for storing lastlogins date and time.
last_login_timestamp: lastlogin
# Latest logout location of the players.
last_location:
world: world
x: x
y: y
z: z
# Enabled only if the bungeecord integration is activated.
server: world
# Support for registrations via WebInterfaces/CSM.
# Disable some backend caching parameters.
disableAggressiveCaching: false
# Main settings
settings:
# ===========================
# Bungeecord integration
# ===========================
bungeecord:
# Enable bungeecord integration features
enabled: true
# Server name (must be unique, please use the name in the bungeecord configuration).
# Use 'auto' for auto configuration (requires the bungeecord module).
serverName: LoginLobby1
# Keep the auth status when the player moves between servers.
# Required if you're using the bungeecord module.
keepAuthBetweenServers: true
# Target server after login
send_after_login:
enabled: false
message: ''
delay: 5
# Server name ("ServerName") or group ("G:GroupName")
# Groups are avariable only when the bungeecord module is avariable.
# If the server change fails the player will be kicked.
target: Lobby1
failKickMessage: 'Failed to connect to the lobby! Please try to join the server again!'
# Target server after logout
send_after_logout:
enabled: false
message: ''
delay: 5
# Server name ("ServerName") or group ("G:GroupName")
# Groups are avariable only when the bungeecord module is avariable.
# If the server change fails the player will be kicked.
target: LoginLobby1
failKickMessage: 'Failed to connect to the lobby! Please try to join the server again!'
# Variables:
# %p playername
bungee_commands:
player_command_after_register:
enabled: false
cmd: ''
console_command_after_register:
enabled: false
cmd: 'alert %p joined for the first time the network!'
player_command_after_login:
enabled: false
cmd: 'glist'
console_command_after_login:
enabled: false
cmd: 'alert %p logged in correctly!'
player_command_after_join:
enabled: false
cmd: ''
console_command_after_join:
enabled: false
cmd: 'alert %p joined the network!'
player_command_first_join:
enabled: false
cmd: ''
console_command_first_join:
enabled: false
cmd: 'alert %p joined for the first time the network!'
# ===========================
# Sessions configuration.
# ===========================
sessions:
# Enable sessions.
# When a player is authenticated, his IP and his nickname is saved.
# The next time the player will join the server, if his IP is the same
# of the last time, and the timeout time hasn't expired, he will be
# automatically authenticated.
enabled: false
# Session timeout.
# 0 for unlimited time (Very dangerous, use it at your own risk!)
# Consider that if player's ip has changed but the timeout hasn't
# expired, player will be kicked out of the sever!
timeout: 10
# When enabled a player's session will expire if someone tries to
# login with a different IP Address.
expire_on_ip_change: true
# ===========================
# Registration settings.
# ===========================
registration:
# After how many time unregistered players should be kicked?
# Set to 0 to disable. (default: 30)
timeout: 30
nickname:
min_length: 4
max_lenght: 16
# Regex syntax.
allowed_characters: '[a-zA-Z0-9_]*'
password:
# Enable double check of password on registration:
# /register <password> <confirmPassword>
double_check: true
# Minimum password lenght.
min_length: 5
# Regex syntax.
allowed_characters: '[\x21-\x7E]*'
# Denied unsafe passwords.
unsafePasswords:
- '123456'
- 'password'
- 'qwerty'
- '12345'
- '54321'
# ===========================
# Login settings.
# ===========================
login:
# After how many time unlogged players should be kicked?
# Set to 0 to disable. (default: 30)
timeout: 30
# ===========================
# Encryption parameters.
# ===========================
password_encryption:
# The hashing algorithm.
# Possible values: MD5, SHA1, SHA256, WHIRLPOOL, XAUTH, MD5VB, PHPBB, MYBB, IPB3,
# PHPFUSION, SMF, XENFORO, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512, DOUBLEMD5,
# PBKDF2, PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM (developpers only).
encryption_algorithm: SHA256
# The salt length for the SALTED2MD5 and MD5(MD5(password)+salt) algorithms.
md5_salt_length: 8
# If password check fails try all the other hash algorithm.
# AuthMe will update the password to the new passwordHash.
enable_convertion: false
# ===========================
# Unlogged user restrictions.
# ===========================
unlogged_restrictions:
# Deny chat messages send for unlogged users.
deny_chat: true
# Hide chat to unlogged users.
# Only player messages, plugins will be able to send messages to the player anyway.
hide_chat: false
# Deny any command message not in the whitelist below.
deny_commands: true
command_whitelist:
- /login
- /register
- /l
- /reg
- /email
- /captcha
movements:
# Restrict player movements.
restrict: true
# Allowed radius.
allowed_radius: 0
# Should unlogged players have speed = 0?
# After the login the walking/flying speeed will be reset to the default value.
removeSpeed: true
# End is there atm xD
# This option will save the quit location of the players.
SaveQuitLocation: false
# Should not logged in players be teleported to the spawn?
# After the authentication, if SaveQuitLocation is enabled,
# they will be teleported back to their normal position.
teleportUnAuthedToSpawn: false
# If enabled, after the login, if the ForceSpawnOnTheseWorlds setting contains
# the player's world, he will be teleported to the world spawnpoint.
# The quit location of the player will be overwritten.
# This is different from "teleportUnAuthedToSpawn" that teleports player
# back to his quit location after the authentication.
ForceSpawnLocOnJoinEnabled: false
# WorldNames where we need to force the spawn location
# Warning: This setting is Case Sensitive!
ForceSpawnOnTheseWorlds:
- world
- world_nether
- world_the_end
# this is very important options,
# every time player join the server,
# if they are registered, AuthMe will switch him
# to unLoggedInGroup, this
# should prevent all major exploit.
# So you can set up on your Permission Plugin
# this special group with 0 permissions, or permissions to chat,
# or permission to
# send private message or all other perms that you want,
# the better way is to set up
# this group with few permissions,
# so if player try to exploit some account,
# they can
# do anything except what you set in perm Group.
# After a correct logged-in player will be
# moved to his correct permissions group!
# Pay attention group name is case sensitive,
# so Admin is different from admin,
# otherwise your group will be wiped,
# and player join in default group []!
# Example unLoggedinGroup: NotLogged
unLoggedinGroup: unLoggedinGroup
# ===========================
# Address restrictions
# ===========================
# Max number of registrations per IP (default: 1)
maxRegPerIp: 1
# Maximum allowed number of Logins per IP, 0 to disable (default: 0)
maxLoginPerIp: 0
# Maximum allowed number of Joins per IP, 0 to disable (default: 0)
maxJoinPerIp: 0
# When this setting is enabled, online players can't be kicked out
# due to "Logged in from another Location"
# This setting will prevent potential security exploits.
ForceSingleSession: true
# To activate the restricted user feature you need
# to enable this option and configure the
# AllowedRestrictedUser field.
AllowRestrictedUser: false
# The restricted user feature will kick players listed below
# if they dont match of the defined ip address.
# Example:
# AllowedRestrictedUser:
# - playername;127.0.0.1
AllowedRestrictedUser:
- playername;127.0.0.
# Ban ip when the ip is not the ip registered in database
banUnsafedIP: false
# ===============================
# Other restrictions
# ===============================
# Should we protect the player inventory before logging in?
# Warning: Requires the latest version of ProtocolLib!
ProtectInventoryBeforeLogIn: true
# Should unregistered players be kicked immediately?
kickNonRegistered: false
# Should players be kicked on wrong password?
kickOnWrongPassword: false
# Should we display all other accounts of a player when he joins?
# Required permission: authme.admin.accounts
displayOtherAccounts: true
# ===============================
# Restrictions compatibility
# ===============================
# Spawn Priority. Avariable values : authme, essentials, multiverse, default
spawnPriority: authme,essentials,multiverse,default
# AuthMe will NEVER teleport players!
noTeleport: false
GameMode:
# Do you want to set player's gamemode to survival when he joins?
# This enables also the settings below.
ForceSurvivalMode: false
# Do you want to reset player's inventory if player joins with creative mode?
ResetInventoryIfCreative: false
# Do you want to force the survival mode ONLY after the /login process?
ForceOnlyAfterLogin: false
# sgdc3: Ok, our configuration is shit.... xD Today I will stop there
registration:
# enable registration on the server?
enabled: true
# Send every X seconds a message to a player to
# remind him that he has to login/register
messageInterval: 5
# Only registered and logged in players can play.
# See restrictions for exceptions
force: true
# Does we replace password registration by an Email registration method ?
enableEmailRegistrationSystem: false
# Enable double check of email when you register
# when it's true, registration require that kind of command:
# /register <email> <confirmEmail>
doubleEmailCheck: false
# Do we force kicking player after a successful registration ?
# Do not use with login feature below
forceKickAfterRegister: false
# Does AuthMe need to enforce a /login after a successful registration ?
forceLoginAfterRegister: false
unrestrictions:
# below you can list all your account name, that
# AuthMe will ignore for registration or login, configure it
# at your own risk!! Remember that if you are going to add
# nickname with [], you have to delimit name with ' '.
# this option add compatibility with BuildCraft and some
# other mods.
# It is CaseSensitive!
UnrestrictedName: []
# Message language, available : en, de, br, cz, pl, fr, ru, hu, sk, es, zhtw, fi, zhcn, lt, it, ko, pt
messagesLanguage: en
# Force these commands after /login, without any '/', use %p for replace with player name
forceCommands: []
# Force these commands after /login as a server console, without any '/', use %p for replace with player name
forceCommandsAsConsole: []
# Force these commands after /register, without any '/', use %p for replace with player name
forceRegisterCommands: []
# Force these commands after /register as a server console, without any '/', use %p for replace with player name
forceRegisterCommandsAsConsole: []
# Do we need to broadcast the welcome message to all server or only to the player? set true for server or false for player
broadcastWelcomeMessage: false
# Do we need to delay the join/leave message to be displayed only when the player is authenticated ?
delayJoinMessage: false
removeJoinMessage: false
removeLeaveMessage: false
# Do we need to add potion effect Blinding before login/register ?
applyBlindEffect: false
ExternalBoardOptions:
# MySQL column for the salt , needed for some forum/cms support
mySQLColumnSalt: ''
# MySQL column for the group, needed for some forum/cms support
mySQLColumnGroup: ''
# -1 mean disabled. If u want that only
# activated player can login in your server
# u can put in this options the group number
# of unactivated user, needed for some forum/cms support
nonActivedUserGroup: -1
# Other MySQL columns where we need to put the Username (case sensitive)
mySQLOtherUsernameColumns: []
# How much Log to Round needed in BCrypt(do not change it if you do not know what's your doing)
bCryptLog2Round: 10
# phpBB prefix defined during phpbb installation process
phpbbTablePrefix: 'phpbb_'
# phpBB activated group id , 2 is default registered group defined by phpbb
phpbbActivatedGroupId: 2
# WordPress prefix defined during WordPress installation process
wordpressTablePrefix: 'wp_'
permission:
# Take care with this options, if you dont want
# to use Vault and Group Switching of
# AuthMe for unloggedIn players put true
# below, default is false.
EnablePermissionCheck: false
BackupSystem:
# Enable or Disable Automatic Backup
ActivateBackup: false
# set Backup at every start of Server
OnServerStart: false
# set Backup at every stop of Server
OnServerStop: true
# Windows only mysql installation Path
MysqlWindowsPath: 'C:\\Program Files\\MySQL\\MySQL Server 5.1\\'
Security:
SQLProblem:
# Stop the server if we can't contact the sql database
# Take care with this, if you set that to false,
# AuthMe automatically disable and the server is not protected!
stopServer: true
ReloadCommand:
# /reload support
useReloadCommandSupport: true
console:
# Remove spam console
noConsoleSpam: false
captcha:
# Player need to put a captcha when he fails too lot the password
useCaptcha: false
# Max allowed tries before request a captcha
maxLoginTry: 5
# Captcha length
captchaLength: 5
Converter:
Rakamak:
# Rakamak file name
fileName: users.rak
# Rakamak use ip ?
useIP: false
# IP file name for rakamak
ipFileName: UsersIp.rak
CrazyLogin:
# CrazyLogin database file
fileName: accounts.db
Email:
# Email SMTP server host
mailSMTP: smtp.gmail.com
# Email SMTP server port
mailPort: 465
# Email account that send the mail
mailAccount: ''
# Email account password
mailPassword: ''
# Custom SenderName, that replace the mailAccount name in the email
mailSenderName: ''
# Random password length
RecoveryPasswordLength: 8
# Email subject of password get
mailSubject: 'Your new AuthMe Password'
# Email text here
mailText: 'Dear <playername>, <br /><br /> This is your new AuthMe password for the server <br /><br /> <servername> : <br /><br /> <generatedpass><br /><br />Do not forget to change password after login! <br /> /changepassword <generatedpass> newPassword'
# Like maxRegPerIp but with email
maxRegPerEmail: 1
# Recall players to add an email ?
recallPlayers: false
# Delay in minute for the recall scheduler
delayRecall: 5
# Blacklist these domains for emails
emailBlacklisted:
- 10minutemail.com
# WhiteList only these domains for emails
emailWhitelisted: []
# Do we need to send new password draw in an image ?
generateImage: false
Hooks:
# Do we need to hook with multiverse for spawn checking?
multiverse: true
# Do we need to hook with BungeeCord for get the real Player ip ?
bungeecord: false
# Do we need to disable Essentials SocialSpy on join ?
disableSocialSpy: true
# Do we need to force /motd Essentials command on join ?
useEssentialsMotd: false
# Do we need to cache custom Attributes ?
customAttributes: false
Purge:
# On Enable , does AuthMe need to purge automatically old accounts unused ?
useAutoPurge: false
# Number of Days an account become Unused
daysBeforeRemovePlayer: 60
# Do we need to remove the player.dat file during purge process ?
removePlayerDat: false
# Do we need to remove the Essentials/users/player.yml file during purge process ?
removeEssentialsFile: false
# World where are players.dat stores
defaultWorld: 'world'
# Do we need to remove LimitedCreative/inventories/player.yml , player_creative.yml files during purge process ?
removeLimitedCreativesInventories: false
# Do we need to remove the AntiXRayData/PlayerData/player file during purge process ?
removeAntiXRayFile: false
# Do we need to remove permissions ?
removePermissions: false
Protection:
# Enable some servers protection ( country based login, antibot )
enableProtection: false
# Countries allowed to join the server and register, see http://dev.bukkit.org/bukkit-plugins/authme-reloaded/pages/countries-codes/ for countries' codes
countries:
- US
- GB
# Countries blacklisted automatically ( without any needed to enable protection )
countriesBlacklist:
- A1
# Do we need to enable automatic antibot system?
enableAntiBot: false
# Max number of player allowed to login in 5 secs before enable AntiBot system automatically
antiBotSensibility: 5
# Duration in minutes of the antibot automatic system
antiBotDuration: 10
VeryGames:
# These features are only available on VeryGames Server Provider
enableIpCheck: false

199
samples/NewPlugin.yml Normal file
View File

@ -0,0 +1,199 @@
name: ${pluginName}
authors: [${pluginAuthors}]
website: ${project.url}
description: ${project.description}
main: ${mainClass}
version: ${project.version}-b${buildNumber}
softdepend:
- Vault
- PermissionsBukkit
- PermissionsEX
- EssentialsGroupManager
- Multiverse-Core
- Essentials
- EssentialsSpawn
- ProtocolLib
commands:
authme:
description: AuthMe admin commands
usage: '/authme reload|register playername password|changepassword playername password|unregister playername|version|converter datatype'
permission: authme.admin
register:
description: Register an account
usage: /register password confirmpassword
aliases: [reg]
permission: authme.player.register
login:
description: Login into a account
usage: /login password
aliases: [l,log]
permission: authme.player.login
changepassword:
description: Change password of a account
usage: /changepassword oldPassword newPassword
permission: authme.player.changepassword
logout:
description: Logout from the server
usage: /logout
permission: authme.player.logout
unregister:
description: unregister your account
usage: /unregister password
permission: authme.player.unregister
email:
description: Add Email or recover password
usage: '/email add your@email.com your@email.com|change oldEmail newEmail|recovery your@email.com'
permission: authme.player.email
captcha:
description: Captcha command
usage: /captcha theCaptcha
permission: authme.player.captcha
permissions:
authme.canbeforced:
description: Allow the user to be forced-logged via API
default: true
authme.player:
description: Gives access to all authme player commands
default: true
children:
authme.player.login: true
authme.player.logout: true
authme.player.register: true
authme.player.unregister: true
authme.player.changepassword: true
authme.player.captcha: true
authme.player.email: true
authme.player.register:
description: Register your account
default: false
authme.player.unregister:
description: Unregister your account
default: false
authme.player.login:
description: Login into your account
default: false
authme.player.logout:
description: Logout from your account
default: false
authme.player.changepassword:
description: Change password of your account
default: false
authme.player.email:
description: Gives access to player's email commands
default: false
children:
authme.player.email.add: true
authme.player.email.change: true
authme.player.email.recover: true
authme.player.email.add:
description: Add an email to your account
default: false
authme.player.email.change:
description: Change email of your account
default: false
authme.player.email.recover:
description: Recover your account
default: false
authme.player.captcha:
description: Captcha command
default: false
authme.admin:
description: Gives access to all authme admin commands
default: op
children:
authme.admin.forcelogin: true
authme.admin.forcelogout: true
authme.admin.register: true
authme.admin.unregister: true
authme.admin.changemail: true
authme.admin.changepassword: true
authme.admin.lastlogin: true
authme.admin.accounts: true
authme.admin.getemail: true
authme.admin.getip: true
authme.admin.setspawn: true
authme.admin.spawn: true
authme.admin.setfirstspawn: true
authme.admin.firstspawn: true
authme.admin.purge: true
authme.admin.purgebannedplayers: true
authme.admin.purgelastpos: true
authme.admin.converter: true
authme.admin.reload: true
authme.admin.switchantibot: true
authme.admin.seeotheraccounts: true
authme.admin.register:
description: Register an account
default: false
authme.admin.unregister:
description: Unregister an account
default: false
authme.admin.forcelogin:
description: Force login for that player
default: false
authme.admin.forcelogout:
description: Force logout for that player
default: false
authme.admin.changepassword:
description: Change the password of an account
default: false
authme.admin.getemail:
description: Get last email about a player
default: false
authme.admin.changeemail:
description: Change a player email
default: false
authme.admin.accounts:
description: Display Players Accounts
default: false
authme.admin.seeotheraccounts:
description: Display other accounts about a player when he logs in
default: false
authme.admin.lastlogin:
description: Get last login date about a player
default: false
authme.admin.getip:
description: Get IP from a player (fake and real)
default: false
authme.admin.setspawn:
description: Set the AuthMe spawn point
default: false
authme.admin.spawn:
description: Teleport to AuthMe spawn point
default: false
authme.admin.setfirstspawn:
description: Set the AuthMe First Spawn Point
default: false
authme.admin.firstspawn:
description: Teleport to AuthMe First Spawn Point
default: false
authme.admin.switchantibot:
description: Switch AntiBot mode on/off
default: false
authme.admin.purge:
description: Database purge command
default: false
authme.admin.purgebannedplayers:
description: Purge banned players
default: false
authme.admin.purgelastpos:
description: Purge last position of a player/players
default: false
authme.admin.converter:
description: Allow the /authme converter command
default: false
authme.admin.reload:
description: Reload the plugin
default: false
authme.vip:
description: Allow vip slot when the server is full
default: false
authme.bypassantibot:
description: Bypass the AntiBot check
default: false
authme.allowmultipleaccounts:
description: Allow more accounts for same ip
default: false
authme.bypassforcesurvival:
description: Bypass all ForceSurvival features
default: false

45798
samples/databases/xenforo.sql Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,148 @@
<?php
/*****************************************************************************
* AuthMe website integration logic *
* ------------------------------------------------------------------------- *
* Allows interaction with the AuthMe database (registration, password *
* verification). Don't forget to update the AUTHME_TABLE value and your *
* database credentials in getAuthmeMySqli(). *
* *
* Source: https://github.com/AuthMe/AuthMeReloaded/ *
*****************************************************************************/
abstract class AuthMeController {
const AUTHME_TABLE = 'authme';
/**
* Entry point function to check supplied credentials against the AuthMe database.
*
* @param string $username the username
* @param string $password the password
* @return bool true iff the data is correct, false otherwise
*/
function checkPassword($username, $password) {
if (is_scalar($username) && is_scalar($password)) {
$hash = $this->getHashFromDatabase($username);
if ($hash) {
return $this->isValidPassword($password, $hash);
}
}
return false;
}
/**
* Returns whether the user exists in the database or not.
*
* @param string $username the username to check
* @return bool true if the user exists; false otherwise
*/
function isUserRegistered($username) {
$mysqli = $this->getAuthmeMySqli();
if ($mysqli !== null) {
$stmt = $mysqli->prepare('SELECT 1 FROM ' . self::AUTHME_TABLE . ' WHERE username = ?');
$stmt->bind_param('s', $username);
$stmt->execute();
return $stmt->fetch();
}
// Defensive default to true; we actually don't know
return true;
}
/**
* Registers a player with the given username.
*
* @param string $username the username to register
* @param string $password the password to associate to the user
* @param string $email the email (may be empty)
* @return bool whether or not the registration was successful
*/
function register($username, $password, $email) {
$email = $email ? $email : 'your@email.com';
$mysqli = $this->getAuthmeMySqli();
if ($mysqli !== null) {
$hash = $this->hash($password);
$stmt = $mysqli->prepare('INSERT INTO ' . self::AUTHME_TABLE . ' (username, realname, password, email, ip) '
. 'VALUES (?, ?, ?, ?, ?)');
$username_low = strtolower($username);
$stmt->bind_param('sssss', $username_low, $username, $hash, $email, $_SERVER['REMOTE_ADDR']);
return $stmt->execute();
}
return false;
}
/**
* Changes password for player.
*
* @param string $username the username
* @param string $password the password
* @return bool true whether or not password change was successful
*/
function changePassword($username, $password) {
$mysqli = $this->getAuthmeMySqli();
if ($mysqli !== null) {
$hash = $this->hash($password);
$stmt = $mysqli->prepare('UPDATE ' . self::AUTHME_TABLE . ' SET password=? '
. 'WHERE username=?');
$username_low = strtolower($username);
$stmt->bind_param('ss', $hash, $username_low);
return $stmt->execute();
}
return false;
}
/**
* Hashes the given password.
*
* @param $password string the clear-text password to hash
* @return string the resulting hash
*/
protected abstract function hash($password);
/**
* Checks whether the given password matches the hash.
*
* @param $password string the clear-text password
* @param $hash string the password hash
* @return boolean true if the password matches, false otherwise
*/
protected abstract function isValidPassword($password, $hash);
/**
* Returns a connection to the database.
*
* @return mysqli|null the mysqli object or null upon error
*/
private function getAuthmeMySqli() {
// CHANGE YOUR DATABASE DETAILS HERE BELOW: host, user, password, database name
$mysqli = new mysqli('localhost', 'root', '', 'authme');
if (mysqli_connect_error()) {
printf('Could not connect to AuthMe database. Errno: %d, error: "%s"',
mysqli_connect_errno(), mysqli_connect_error());
return null;
}
return $mysqli;
}
/**
* Retrieves the hash associated with the given user from the database.
*
* @param string $username the username whose hash should be retrieved
* @return string|null the hash, or null if unavailable (e.g. username doesn't exist)
*/
private function getHashFromDatabase($username) {
$mysqli = $this->getAuthmeMySqli();
if ($mysqli !== null) {
$stmt = $mysqli->prepare('SELECT password FROM ' . self::AUTHME_TABLE . ' WHERE username = ?');
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->bind_result($password);
if ($stmt->fetch()) {
return $password;
}
}
return null;
}
}

View File

@ -0,0 +1,20 @@
<?php
/***********************************************************
* AuthMe website integration logic for BCrypt *
* ------------------------------------------------------- *
* See AuthMeController for details. *
* *
* Source: https://github.com/AuthMe/AuthMeReloaded/ *
***********************************************************/
class Bcrypt extends AuthMeController {
protected function hash($password) {
return password_hash($password, PASSWORD_BCRYPT);
}
protected function isValidPassword($password, $hash) {
return password_verify($password, $hash);
}
}

View File

@ -0,0 +1,53 @@
<?php
/***********************************************************
* AuthMe website integration logic for PBKDF2 *
* ------------------------------------------------------- *
* See AuthMeController for details. *
* *
* Source: https://github.com/AuthMe/AuthMeReloaded/ *
***********************************************************/
class Pbkdf2 extends AuthMeController {
/** @var string[] range of characters for salt generation */
private $CHARS;
const SALT_LENGTH = 16;
const NUMBER_OF_ITERATIONS = 10000;
public function __construct() {
$this->CHARS = self::initCharRange();
}
protected function isValidPassword($password, $hash) {
// hash := pbkdf2_sha256$iterations$salt$hash
$parts = explode('$', $hash);
return count($parts) === 4 && $hash === $this->computeHash($parts[1], $parts[2], $password);
}
protected function hash($password) {
$salt = $this->generateSalt();
return $this->computeHash(self::NUMBER_OF_ITERATIONS, $salt, $password);
}
private function computeHash($iterations, $salt, $password) {
return 'pbkdf2_sha256$' . self::NUMBER_OF_ITERATIONS . '$' . $salt
. '$' . hash_pbkdf2('sha256', $password, $salt, self::NUMBER_OF_ITERATIONS, 64, false);
}
/**
* @return string randomly generated salt
*/
private function generateSalt() {
$maxCharIndex = count($this->CHARS) - 1;
$salt = '';
for ($i = 0; $i < self::SALT_LENGTH; ++$i) {
$salt .= $this->CHARS[mt_rand(0, $maxCharIndex)];
}
return $salt;
}
private static function initCharRange() {
return array_merge(range('0', '9'), range('a', 'f'));
}
}

View File

@ -0,0 +1,48 @@
<?php
/***********************************************************
* AuthMe website integration logic for SHA256 *
* ------------------------------------------------------- *
* See AuthMeController for details. *
* *
* Source: https://github.com/AuthMe/AuthMeReloaded/ *
***********************************************************/
class Sha256 extends AuthMeController {
/** @var string[] range of characters for salt generation */
private $CHARS;
const SALT_LENGTH = 16;
public function __construct() {
$this->CHARS = self::initCharRange();
}
protected function isValidPassword($password, $hash) {
// $SHA$salt$hash, where hash := sha256(sha256(password) . salt)
$parts = explode('$', $hash);
return count($parts) === 4 && $parts[3] === hash('sha256', hash('sha256', $password) . $parts[2]);
}
protected function hash($password) {
$salt = $this->generateSalt();
return '$SHA$' . $salt . '$' . hash('sha256', hash('sha256', $password) . $salt);
}
/**
* @return string randomly generated salt
*/
private function generateSalt() {
$maxCharIndex = count($this->CHARS) - 1;
$salt = '';
for ($i = 0; $i < self::SALT_LENGTH; ++$i) {
$salt .= $this->CHARS[mt_rand(0, $maxCharIndex)];
}
return $salt;
}
private static function initCharRange() {
return array_merge(range('0', '9'), range('a', 'f'));
}
}

View File

@ -0,0 +1,102 @@
<!--
This is a demo page for AuthMe website integration.
See AuthMeController.php and the extending classes for the PHP code you need.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<title>AuthMe Integration Sample</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<?php
error_reporting(E_ALL);
require 'AuthMeController.php';
// Change this to the file of the hash encryption you need, e.g. Bcrypt.php or Sha256.php
require 'Sha256.php';
// The class name must correspond to the file you have in require above! e.g. require 'Sha256.php'; and new Sha256();
$authme_controller = new Sha256();
$action = get_from_post_or_empty('action');
$user = get_from_post_or_empty('username');
$pass = get_from_post_or_empty('password');
$email = get_from_post_or_empty('email');
$was_successful = false;
if ($action && $user && $pass) {
if ($action === 'Log in') {
$was_successful = process_login($user, $pass, $authme_controller);
} else if ($action === 'Register') {
$was_successful = process_register($user, $pass, $email, $authme_controller);
}
}
if (!$was_successful) {
echo '<h1>Login sample</h1>
This is a demo form for AuthMe website integration. Enter your AuthMe login details
into the following form to test it.
<form method="post">
<table>
<tr><td>Name</td><td><input type="text" value="' . htmlspecialchars($user) . '" name="username" /></td></tr>
<tr><td>Email</td><td><input type="text" value="' . htmlspecialchars($email) . '" name="email" /></td></tr>
<tr><td>Pass</td><td><input type="password" value="' . htmlspecialchars($pass) . '" name="password" /></td></tr>
<tr>
<td><input type="submit" name="action" value="Log in" /></td>
<td><input type="submit" name="action" value="Register" /></td>
</tr>
</table>
</form>';
}
function get_from_post_or_empty($index_name) {
return trim(
filter_input(INPUT_POST, $index_name, FILTER_UNSAFE_RAW, FILTER_REQUIRE_SCALAR | FILTER_FLAG_STRIP_LOW)
?: '');
}
// Login logic
function process_login($user, $pass, AuthMeController $controller) {
if ($controller->checkPassword($user, $pass)) {
printf('<h1>Hello, %s!</h1>', htmlspecialchars($user));
echo 'Successful login. Nice to have you back!'
. '<br /><a href="index.php">Back to form</a>';
return true;
} else {
echo '<h1>Error</h1> Invalid username or password.';
}
return false;
}
// Register logic
function process_register($user, $pass, $email, AuthMeController $controller) {
if ($controller->isUserRegistered($user)) {
echo '<h1>Error</h1> This user already exists.';
} else if (!is_email_valid($email)) {
echo '<h1>Error</h1> The supplied email is invalid.';
} else {
// Note that we don't validate the password or username at all in this demo...
$register_success = $controller->register($user, $pass, $email);
if ($register_success) {
printf('<h1>Welcome, %s!</h1>Thanks for registering', htmlspecialchars($user));
echo '<br /><a href="index.php">Back to form</a>';
return true;
} else {
echo '<h1>Error</h1>Unfortunately, there was an error during the registration.';
}
}
return false;
}
function is_email_valid($email) {
return trim($email) === ''
? true // accept no email
: filter_var($email, FILTER_VALIDATE_EMAIL);
}
?>
</body>
</html>

View File

@ -0,0 +1,510 @@
package fr.xephi.authme;
import ch.jalu.injector.Injector;
import ch.jalu.injector.InjectorBuilder;
import fr.xephi.authme.api.v3.AuthMeApi;
import fr.xephi.authme.command.CommandHandler;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.initialization.DataSourceProvider;
import fr.xephi.authme.initialization.OnShutdownPlayerSaver;
import fr.xephi.authme.initialization.OnStartupTasks;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import fr.xephi.authme.initialization.SettingsProvider;
import fr.xephi.authme.initialization.TaskCloser;
import fr.xephi.authme.listener.BlockListener;
import fr.xephi.authme.listener.EntityListener;
import fr.xephi.authme.listener.PlayerListener;
import fr.xephi.authme.listener.PlayerListener111;
import fr.xephi.authme.listener.PlayerListener19;
import fr.xephi.authme.listener.PlayerListener19Spigot;
import fr.xephi.authme.listener.PlayerQuitListener;
import fr.xephi.authme.listener.GuiCaptchaHandler;
import fr.xephi.authme.listener.ServerListener;
import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.security.crypts.Sha256;
import fr.xephi.authme.service.BackupService;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.MigrationService;
import fr.xephi.authme.service.bungeecord.BungeeReceiver;
import fr.xephi.authme.service.yaml.YamlParseException;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.SettingsWarner;
import fr.xephi.authme.settings.properties.EmailSettings;
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.ExceptionUtils;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler;
import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
import java.util.function.Consumer;
import java.util.logging.Level;
import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE;
import static fr.xephi.authme.util.Utils.isClassLoaded;
/**
* The AuthMe main class.
*/
public class AuthMe extends JavaPlugin {
// Constants
private static final String PLUGIN_NAME = "AuthMeReloaded";
private static final String LOG_FILENAME = "authme.log";
private static final int CLEANUP_INTERVAL = 5 * TICKS_PER_MINUTE;
// Version and build number values
private static String pluginVersion = "5.6.0-Fork";
private static final String pluginBuild = "b";
private static String pluginBuildNumber = "22";
protected final Boolean SHAEnabled = false;
// Private instances
private EmailService emailService;
private CommandHandler commandHandler;
@Inject
public static Settings settings;
private DataSource database;
private BukkitService bukkitService;
private Injector injector;
private BackupService backupService;
private ConsoleLogger logger;
/**
* Constructor.
*/
public AuthMe() {
}
/**
* Get the plugin's name.
*
* @return The plugin's name.
*/
public static String getPluginName() {
return PLUGIN_NAME;
}
/**
* Get the plugin's version.
*
* @return The plugin's version.
*/
public static String getPluginVersion() {
return pluginVersion;
}
/**
* Get the plugin's build number.
*
* @return The plugin's build number.
*/
public static String getPluginBuildNumber() {
return pluginBuildNumber;
}
/**
* Method called when the server enables the plugin.
*/
@Override
public void onEnable() {
// Load the plugin version data from the plugin description file
loadPluginInfo(getDescription().getVersion());
// Set the Logger instance and log file path
ConsoleLogger.initialize(getLogger(), new File(getDataFolder(), LOG_FILENAME));
logger = ConsoleLoggerFactory.get(AuthMe.class);
logger.info("You are running an unofficial fork version of AuthMe!");
// Check server version
if (!isClassLoaded("org.spigotmc.event.player.PlayerSpawnLocationEvent")
|| !isClassLoaded("org.bukkit.event.player.PlayerInteractAtEntityEvent")) {
logger.warning("你正在运行不受支持的服务器版本 (" + getServerNameVersionSafe() + "). "
+ "AuthMe 仅支持Spigot 1.9及之后的版本!");
stopOrUnload();
return;
}
// Prevent running AuthMeBridge due to major exploit issues
if (getServer().getPluginManager().isPluginEnabled("AuthMeBridge")) {
logger.warning("检测到 AuthMeBridge被加载, 对AuthMeBridge的支持已经停止 "
+ "且可能会造成严重漏洞! 已中止加载!");
stopOrUnload();
return;
}
// Initialize the plugin
try {
initialize();
} catch (Throwable th) {
YamlParseException yamlParseException = ExceptionUtils.findThrowableInCause(YamlParseException.class, th);
if (yamlParseException == null) {
logger.logException("已中止AuthMeReReloaded的初始化,原因:", th);
th.printStackTrace();
} else {
logger.logException("文件 '" + yamlParseException.getFile() + "' 包含YAML语法错误. "
+ "请尝试在 https://yamllint.com 中检查文件内容", yamlParseException);
}
stopOrUnload();
return;
}
// Show settings warnings
injector.getSingleton(SettingsWarner.class).logWarningsForMisconfigurations();
// Schedule clean up task
CleanupTask cleanupTask = injector.getSingleton(CleanupTask.class);
cleanupTask.runTaskTimerAsynchronously(this, CLEANUP_INTERVAL, CLEANUP_INTERVAL);
// Do a backup on start
backupService.doBackup(BackupService.BackupCause.START);
// Set up Metrics
OnStartupTasks.sendMetrics(this, settings);
// Successful message
//detect server brand with classloader
checkServerType();
logger.info("AuthMeReReloaded is enabled successfully!");
// Purge on start if enabled
PurgeService purgeService = injector.getSingleton(PurgeService.class);
purgeService.runAutoPurge();
// 注册玩家退出事件监听
if (settings.getProperty(SecuritySettings.ANTI_GHOST_PLAYERS) || settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)/* || settings.getProperty(SecuritySettings.GUI_CAPTCHA)*/) {
if (settings.getProperty(SecuritySettings.ANTI_GHOST_PLAYERS)) {
getServer().getPluginManager().registerEvents(new PlayerQuitListener((Plugin) this), this);
}
if (settings.getProperty(SecuritySettings.GUI_CAPTCHA) && getServer().getPluginManager().isPluginEnabled("ProtocolLib")) {
getServer().getPluginManager().registerEvents(new GuiCaptchaHandler((Plugin) this), this);
logger.info("(Alpha3)GUICaptcha Feature is enabled successfully!");
logger.info("These features are still in development, if you encountered any problem, please report.");
} else if (settings.getProperty(SecuritySettings.GUI_CAPTCHA) && !getServer().getPluginManager().isPluginEnabled("ProtocolLib")) {
logger.warning("ProtocolLib is not loaded, we can't enable GUI Captcha.");
}
//logger.info("以上功能尚在测试中,如有问题请反馈,如需关闭请前往config.yml修改");
logger.info("GitHub Issue: github.com/HaHaWTH/AuthMeReReloaded/issues");
}
if (settings.getProperty(SecuritySettings.CHECK_FOR_UPDATES)) {
checkForUpdates();
}
if (SHAEnabled){
//shaChecker();
}
}
public File pluginfile = getFile();
/**
* Load the version and build number of the plugin from the description file.
*
* @param versionRaw the version as given by the plugin description file
*/
private static void loadPluginInfo(String versionRaw) {
int index = versionRaw.lastIndexOf("-");
if (index != -1) {
pluginVersion = versionRaw.substring(0, index);
pluginBuildNumber = versionRaw.substring(index + 1);
if (pluginBuildNumber.startsWith("b")) {
pluginBuildNumber = pluginBuildNumber.substring(1);
}
}
}
/**
* Initialize the plugin and all the services.
*/
private void initialize() {
// 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.register(AuthMe.class, this);
injector.register(Server.class, getServer());
injector.register(PluginManager.class, getServer().getPluginManager());
injector.register(BukkitScheduler.class, getServer().getScheduler());
injector.provide(DataFolder.class, getDataFolder());
injector.registerProvider(Settings.class, SettingsProvider.class);
injector.registerProvider(DataSource.class, DataSourceProvider.class);
// Get settings and set up logger
settings = injector.getSingleton(Settings.class);
ConsoleLoggerFactory.reloadSettings(settings);
OnStartupTasks.setupConsoleFilter(getLogger());
// Set all service fields on the AuthMe class
instantiateServices(injector);
// Convert deprecated PLAINTEXT hash entries
MigrationService.changePlainTextToSha256(settings, database, new Sha256());
// If the server is empty (fresh start) just set all the players as unlogged
if (bukkitService.getOnlinePlayers().isEmpty()) {
database.purgeLogged();
}
// Register event listeners
registerEventListeners(injector);
// Start Email recall task if needed
OnStartupTasks onStartupTasks = injector.newInstance(OnStartupTasks.class);
onStartupTasks.scheduleRecallEmailTask();
}
/**
* Instantiates all services.
*
* @param injector the injector
*/
void instantiateServices(Injector injector) {
database = injector.getSingleton(DataSource.class);
bukkitService = injector.getSingleton(BukkitService.class);
commandHandler = injector.getSingleton(CommandHandler.class);
emailService = injector.getSingleton(EmailService.class);
backupService = injector.getSingleton(BackupService.class);
// Trigger instantiation (class not used elsewhere)
injector.getSingleton(BungeeReceiver.class);
// Trigger construction of API classes; they will keep track of the singleton
injector.getSingleton(AuthMeApi.class);
}
/**
* Registers all event listeners.
*
* @param injector the injector
*/
void registerEventListeners(Injector injector) {
// Get the plugin manager instance
PluginManager pluginManager = getServer().getPluginManager();
// Register event listeners
pluginManager.registerEvents(injector.getSingleton(PlayerListener.class), this);
pluginManager.registerEvents(injector.getSingleton(BlockListener.class), this);
pluginManager.registerEvents(injector.getSingleton(EntityListener.class), this);
pluginManager.registerEvents(injector.getSingleton(ServerListener.class), this);
// Try to register 1.9 player listeners
if (isClassLoaded("org.bukkit.event.player.PlayerSwapHandItemsEvent")) {
pluginManager.registerEvents(injector.getSingleton(PlayerListener19.class), this);
}
// Try to register 1.9 spigot player listeners
if (isClassLoaded("org.spigotmc.event.player.PlayerSpawnLocationEvent")) {
pluginManager.registerEvents(injector.getSingleton(PlayerListener19Spigot.class), this);
}
// Register listener for 1.11 events if available
if (isClassLoaded("org.bukkit.event.entity.EntityAirChangeEvent")) {
pluginManager.registerEvents(injector.getSingleton(PlayerListener111.class), this);
}
}
/**
* Stops the server or disables the plugin, as defined in the configuration.
*/
public void stopOrUnload() {
if (settings == null || settings.getProperty(SecuritySettings.STOP_SERVER_ON_PROBLEM)) {
getLogger().warning("THE SERVER IS GOING TO SHUT DOWN AS DEFINED IN THE CONFIGURATION!");
setEnabled(false);
getServer().shutdown();
} else {
setEnabled(false);
}
}
@Override
public void onDisable() {
// onDisable is also called when we prematurely abort, so any field may be null
OnShutdownPlayerSaver onShutdownPlayerSaver = injector == null
? null
: injector.createIfHasDependencies(OnShutdownPlayerSaver.class);
if (onShutdownPlayerSaver != null) {
onShutdownPlayerSaver.saveAllPlayers();
}
if (settings.getProperty(EmailSettings.SHUTDOWN_MAIL)){
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy'.'MM'.'dd'.' HH:mm:ss");
Date date = new Date(System.currentTimeMillis());
emailService.sendShutDown(settings.getProperty(EmailSettings.SHUTDOWN_MAIL_ADDRESS),dateFormat.format(date));
}
// Do backup on stop if enabled
if (backupService != null) {
backupService.doBackup(BackupService.BackupCause.STOP);
}
// Wait for tasks and close data source
new TaskCloser(this, database).run();
// Disabled correctly
Consumer<String> infoLogMethod = logger == null ? getLogger()::info : logger::info;
infoLogMethod.accept("AuthMe " + this.getDescription().getVersion() + " is unloaded successfully!");
ConsoleLogger.closeFileWriter();
}
private static final String owner = "HaHaWTH";
private static final String owner_gitee = "Shixuehan114514";
private static final String repo = "AuthMeReReloaded";
private void checkForUpdates() {
logger.info("Checking for updates...");
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
try {
// 从南通集线器获取最新版本号
URL url = new URL("https://api.github.com/repos/" + owner + "/" + repo + "/releases/latest");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10000); // 设置连接超时为10秒
conn.setReadTimeout(10000); // 设置读取超时为10秒
Scanner scanner = new Scanner(conn.getInputStream());
String response = scanner.useDelimiter("\\Z").next();
scanner.close();
// 处理JSON响应
String latestVersion = response.substring(response.indexOf("tag_name") + 11);
latestVersion = latestVersion.substring(0, latestVersion.indexOf("\""));
if ((pluginBuild + pluginBuildNumber).equals(latestVersion)) {
getLogger().log(Level.INFO,"You are running the latest version.");
}
if (!(pluginBuild + pluginBuildNumber).equals(latestVersion)) {
// Display update message
String message = "New version available! Latest:" + latestVersion + " Current:" + pluginBuild + pluginBuildNumber;
getLogger().log(Level.INFO, message);
getLogger().log(Level.INFO,"Download from here:github.com/HaHaWTH/AuthMeReReloaded/releases/latest");
}
}catch (IOException e) {
getLogger().log(Level.WARNING,"Error occurred while checking updates from GitHub. Reason: " + e.getMessage());
}
});
}
private void checkServerType() {
if (isClassLoaded("com.destroystokyo.paper.PaperConfig")) {
logger.info("AuthMeReReloaded is running on Paper");
}else if (isClassLoaded("catserver.server.CatServerConfig")) {
logger.info("AuthMeReReloaded is running on CatServer");
} else if (isClassLoaded("org.spigotmc.SpigotConfig")) {
logger.info("AuthMeReReloaded is running on Spigot");
} else if (isClassLoaded("org.bukkit.craftbukkit.CraftServer")) {
logger.info("AuthMeReReloaded is running on Bukkit");
} else {
logger.info("AuthMeReReloaded is running on Unknown*");
}
}
// 其他方法和事件处理
// 其他方法和事件处理
private static final String SHA_URL = "https://raw.githubusercontent.com/"+ owner +"/"+ repo + "/master/"+pluginBuild +pluginBuildNumber+ ".sha";
private static final String ALGORITHM = "SHA-256";
private static final String PROXY_URL = "https://ghproxy.com/";
private static final String SHA_URL_GITEE = "https://gitee.com/"+ owner_gitee +"/"+ repo + "/raw/master/"+pluginBuild+pluginBuildNumber+ ".sha";
// public void shaChecker() {
// // 请求SHA文件
//
// String actualSha;
// try {
// URL url;
// if(settings.getProperty(SecuritySettings.SHA_CHECK_METHOD).equals("github")) {
// url = new URL(SHA_URL);
// logger.info("正在检查文件完整性...(GitHub)");
// } else if(settings.getProperty(SecuritySettings.SHA_CHECK_METHOD).equals("ghproxy")) {
// url = new URL(PROXY_URL + SHA_URL);
// logger.info("正在检查文件完整性...(GhProxy)");
// } else if (settings.getProperty(SecuritySettings.SHA_CHECK_METHOD).equals("gitee")) {
// url = new URL(SHA_URL_GITEE);
// logger.info("正在检查文件完整性...(Gitee)");
// }else {
// logger.warning("未知的SHA检查方法,将从GitHub获取SHA文件");
// url = new URL(SHA_URL);
// }
//
//
// HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// conn.setConnectTimeout(10000);
// conn.setReadTimeout(9000);
// conn.setRequestMethod("GET");
// InputStream stream = conn.getInputStream();
// ByteArrayOutputStream result = new ByteArrayOutputStream();
// byte[] buffer = new byte[1024];
// int length;
// while ((length = stream.read(buffer)) != -1) {
// result.write(buffer, 0, length);
// }
// String expectedSha = result.toString().trim();
// // 计算插件文件的SHA值
// MessageDigest md = MessageDigest.getInstance(ALGORITHM);
// byte[] fileBytes = Files.readAllBytes(pluginfile.toPath());
// byte[] hashBytes = md.digest(fileBytes);
// StringBuilder sb = new StringBuilder();
// for (byte b : hashBytes) {
// sb.append(String.format("%02x", b));
// }
// actualSha = sb.toString();
//
// // 比较SHA值并加载插件
// if (expectedSha.equals(actualSha)) {
// logger.info("SHA联网安全校验完毕");
// } else {
// // SHA值不匹配插件可能被篡改
// logger.warning("SHA值不匹配,插件被篡改");
// stopOrUnload();
// }
// }catch (NoSuchAlgorithmException | IOException e){
// logger.warning("SHA校验失败,请尝试切换校验API");
// logger.warning("您当前请求的API为:" + settings.getProperty(SecuritySettings.SHA_CHECK_METHOD));
// stopOrUnload();
// }
// }
/**
* Handle Bukkit commands.
*
* @param sender The command sender (Bukkit).
* @param cmd The command (Bukkit).
* @param commandLabel The command label (Bukkit).
* @param args The command arguments (Bukkit).
* @return True if the command was executed, false otherwise.
*/
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd,
@NotNull String commandLabel, String[] args) {
// Make sure the command handler has been initialized
if (commandHandler == null) {
getLogger().severe("AuthMe command handler is not available");
return false;
}
// Handle the command
return commandHandler.processCommand(sender, commandLabel, args);
}
private String getServerNameVersionSafe() {
try {
Server server = getServer();
return server.getName() + " v. " + server.getVersion();
} catch (Throwable ignore) {
return "-";
}
}
}

View File

@ -0,0 +1,287 @@
package fr.xephi.authme;
import com.google.common.base.Throwables;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.ExceptionUtils;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* AuthMe logger.
*/
public final class ConsoleLogger {
private static final String NEW_LINE = System.getProperty("line.separator");
/** Formatter which formats dates to something like "[08-16 21:18:46]" for any given LocalDateTime. */
private static final DateTimeFormatter DATE_FORMAT = new DateTimeFormatterBuilder()
.appendLiteral('[')
.appendPattern("MM-dd HH:mm:ss")
.appendLiteral(']')
.toFormatter();
// Outside references
private static File logFile;
private static Logger logger;
// Shared state
private static OutputStreamWriter fileWriter;
// Individual state
private final String name;
private LogLevel logLevel = LogLevel.INFO;
/**
* Constructor.
*
* @param name the name of this logger (the fully qualified class name using it)
*/
public ConsoleLogger(String name) {
this.name = name;
}
// --------
// Configurations
// --------
public static void initialize(Logger logger, File logFile) {
ConsoleLogger.logger = logger;
ConsoleLogger.logFile = logFile;
}
/**
* Sets logging settings which are shared by all logger instances.
*
* @param settings the settings to read from
*/
public static void initializeSharedSettings(Settings settings) {
boolean useLogging = settings.getProperty(SecuritySettings.USE_LOGGING);
if (useLogging) {
initializeFileWriter();
} else {
closeFileWriter();
}
}
/**
* Sets logging settings which are individual to all loggers.
*
* @param settings the settings to read from
*/
public void initializeSettings(Settings settings) {
this.logLevel = settings.getProperty(PluginSettings.LOG_LEVEL);
}
public LogLevel getLogLevel() {
return logLevel;
}
public String getName() {
return name;
}
// --------
// Logging methods
// --------
/**
* Log a WARN message.
*
* @param message The message to log
*/
public void warning(String message) {
logger.warning(message);
writeLog("[WARN] " + message);
}
/**
* Log a Throwable with the provided message on WARNING level
* and save the stack trace to the log file.
*
* @param message The message to accompany the exception
* @param th The Throwable to log
*/
public void logException(String message, Throwable th) {
warning(message + " " + ExceptionUtils.formatException(th));
writeLog(Throwables.getStackTraceAsString(th));
}
/**
* Log an INFO message.
*
* @param message The message to log
*/
public void info(String message) {
logger.info(message);
writeLog("[INFO] " + message);
}
/**
* Log a FINE message if enabled.
* <p>
* Implementation note: this logs a message on INFO level because
* levels below INFO are disabled by Bukkit/Spigot.
*
* @param message The message to log
*/
public void fine(String message) {
if (logLevel.includes(LogLevel.FINE)) {
logger.info(message);
writeLog("[INFO:FINE] " + message);
}
}
// --------
// Debug log methods
// --------
/**
* Log a DEBUG message if enabled.
* <p>
* Implementation note: this logs a message on INFO level and prefixes it with "DEBUG" because
* levels below INFO are disabled by Bukkit/Spigot.
*
* @param message The message to log
*/
public void debug(String message) {
if (logLevel.includes(LogLevel.DEBUG)) {
logAndWriteWithDebugPrefix(message);
}
}
/**
* Log the DEBUG message.
*
* @param message the message
* @param param1 parameter to replace in the message
*/
// Avoids array creation if DEBUG level is disabled
public void debug(String message, Object param1) {
if (logLevel.includes(LogLevel.DEBUG)) {
debug(message, new Object[]{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 void debug(String message, Object param1, Object param2) {
if (logLevel.includes(LogLevel.DEBUG)) {
debug(message, new Object[]{param1, param2});
}
}
/**
* Log the DEBUG message.
*
* @param message the message
* @param params the params to replace in the message
*/
public void debug(String message, Object... params) {
if (logLevel.includes(LogLevel.DEBUG)) {
logAndWriteWithDebugPrefix(MessageFormat.format(message, params));
}
}
/**
* Log the DEBUG message from the supplier if enabled.
*
* @param msgSupplier the message supplier
*/
public void debug(Supplier<String> msgSupplier) {
if (logLevel.includes(LogLevel.DEBUG)) {
logAndWriteWithDebugPrefix(msgSupplier.get());
}
}
private void logAndWriteWithDebugPrefix(String message) {
String debugMessage = "[INFO:DEBUG] " + message;
logger.info(debugMessage);
writeLog(debugMessage);
}
// --------
// Helpers
// --------
/**
* Closes the file writer.
*/
public static void closeFileWriter() {
if (fileWriter != null) {
try {
fileWriter.flush();
} catch (IOException ignored) {
} finally {
closeSafely(fileWriter);
fileWriter = null;
}
}
}
/**
* Write a message into the log file with a TimeStamp if enabled.
*
* @param message The message to write to the log
*/
private static void writeLog(String message) {
if (fileWriter != null) {
String dateTime = DATE_FORMAT.format(LocalDateTime.now());
try {
fileWriter.write(dateTime);
fileWriter.write(": ");
fileWriter.write(message);
fileWriter.write(NEW_LINE);
fileWriter.flush();
} catch (IOException ignored) {
}
}
}
private static void closeSafely(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
logger.log(Level.SEVERE, "Failed to close resource", e);
}
}
}
/**
* Populates the {@link #fileWriter} field if it is null, handling any exceptions that might
* arise during its creation.
*/
private static void initializeFileWriter() {
if (fileWriter == null) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(logFile, true);
fileWriter = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
} catch (Exception e) {
closeSafely(fos);
logger.log(Level.SEVERE, "Failed to create writer to AuthMe log file", e);
}
}
}
}

View File

@ -0,0 +1,370 @@
package fr.xephi.authme.api.v3;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams;
import fr.xephi.authme.process.register.executors.RegistrationMethod;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.GeoIpService;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
/**
* The current API of AuthMe.
*
* Recommended method of retrieving the AuthMeApi object:
* <code>
* AuthMeApi authmeApi = AuthMeApi.getInstance();
* </code>
*/
public class AuthMeApi {
private static AuthMeApi singleton;
private final AuthMe plugin;
private final DataSource dataSource;
private final PasswordSecurity passwordSecurity;
private final Management management;
private final ValidationService validationService;
private final PlayerCache playerCache;
private final GeoIpService geoIpService;
/*
* Constructor for AuthMeApi.
*/
@Inject
AuthMeApi(AuthMe plugin, DataSource dataSource, PlayerCache playerCache, PasswordSecurity passwordSecurity,
Management management, ValidationService validationService, GeoIpService geoIpService) {
this.plugin = plugin;
this.dataSource = dataSource;
this.passwordSecurity = passwordSecurity;
this.management = management;
this.validationService = validationService;
this.playerCache = playerCache;
this.geoIpService = geoIpService;
AuthMeApi.singleton = this;
}
/**
* Get the AuthMeApi object for AuthMe.
*
* @return The AuthMeApi object, or null if the AuthMe plugin is not enabled or not fully initialized yet
*/
public static AuthMeApi getInstance() {
return singleton;
}
/**
* Return the plugin instance.
*
* @return The AuthMe instance
*/
public AuthMe getPlugin() {
return plugin;
}
/**
* Gather the version number of the plugin.
* This can be used to determine whether certain AuthMeApi features are available or not.
*
* @return Plugin version identifier as a string.
*/
public String getPluginVersion() {
return AuthMe.getPluginVersion();
}
/**
* Return whether the given player is authenticated.
*
* @param player The player to verify
* @return true if the player is authenticated
*/
public boolean isAuthenticated(Player player) {
return playerCache.isAuthenticated(player.getName());
}
/**
* Check whether the given player is an NPC.
*
* @param player The player to verify
* @return true if the player is an npc
*/
public boolean isNpc(Player player) {
return PlayerUtils.isNpc(player);
}
/**
* Check whether the given player is unrestricted. For such players, AuthMe will not require
* them to authenticate.
*
* @param player The player to verify
* @return true if the player is unrestricted
* @see fr.xephi.authme.settings.properties.RestrictionSettings#UNRESTRICTED_NAMES
*/
public boolean isUnrestricted(Player player) {
return validationService.isUnrestricted(player.getName());
}
/**
* Get the last location of an online player.
*
* @param player The player to process
* @return The location of the player
*/
public Location getLastLocation(Player player) {
PlayerAuth auth = playerCache.getAuth(player.getName());
if (auth != null) {
return new Location(Bukkit.getWorld(auth.getWorld()),
auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ(), auth.getYaw(), auth.getPitch());
}
return null;
}
/**
* Returns the AuthMe info of the given player's name, or empty optional if the player doesn't exist.
*
* @param playerName The player name to look up
* @return AuthMe player info, or empty optional if the player doesn't exist
*/
public Optional<AuthMePlayer> getPlayerInfo(String playerName) {
PlayerAuth auth = playerCache.getAuth(playerName);
if (auth == null) {
auth = dataSource.getAuth(playerName);
}
return AuthMePlayerImpl.fromPlayerAuth(auth);
}
/**
* Get the last ip address of a player.
*
* @param playerName The name of the player to process
* @return The last ip address of the player
*/
public String getLastIp(String playerName) {
PlayerAuth auth = playerCache.getAuth(playerName);
if (auth == null) {
auth = dataSource.getAuth(playerName);
}
if (auth != null) {
return auth.getLastIp();
}
return null;
}
/**
* Get user names by ip.
*
* @param address The ip address to process
* @return The list of user names related to the ip address
*/
public List<String> getNamesByIp(String address) {
return dataSource.getAllAuthsByIp(address);
}
/**
* Get the last (AuthMe) login date of a player.
*
* @param playerName The name of the player to process
* @return The date of the last login, or null if the player doesn't exist or has never logged in
* @deprecated Use Java 8's Instant method {@link #getLastLoginTime(String)}
*/
@Deprecated
public Date getLastLogin(String playerName) {
Long lastLogin = getLastLoginMillis(playerName);
return lastLogin == null ? null : new Date(lastLogin);
}
/**
* Get the last (AuthMe) login timestamp of a player.
*
* @param playerName The name of the player to process
*
* @return The timestamp of the last login, or null if the player doesn't exist or has never logged in
*/
public Instant getLastLoginTime(String playerName) {
Long lastLogin = getLastLoginMillis(playerName);
return lastLogin == null ? null : Instant.ofEpochMilli(lastLogin);
}
private Long getLastLoginMillis(String playerName) {
PlayerAuth auth = playerCache.getAuth(playerName);
if (auth == null) {
auth = dataSource.getAuth(playerName);
}
if (auth != null) {
return auth.getLastLogin();
}
return null;
}
/**
* Return whether the player is registered.
*
* @param playerName The player name to check
* @return true if player is registered, false otherwise
*/
public boolean isRegistered(String playerName) {
String player = playerName.toLowerCase(Locale.ROOT);
return dataSource.isAuthAvailable(player);
}
/**
* Check the password for the given player.
*
* @param playerName The player to check the password for
* @param passwordToCheck The password to check
* @return true if the password is correct, false otherwise
*/
public boolean checkPassword(String playerName, String passwordToCheck) {
return passwordSecurity.comparePassword(passwordToCheck, playerName);
}
/**
* Register an OFFLINE/ONLINE player with the given password.
*
* @param playerName The player to register
* @param password The password to register the player with
*
* @return true if the player was registered successfully
*/
public boolean registerPlayer(String playerName, String password) {
String name = playerName.toLowerCase(Locale.ROOT);
if (isRegistered(name)) {
return false;
}
HashedPassword result = passwordSecurity.computeHash(password, name);
PlayerAuth auth = PlayerAuth.builder()
.name(name)
.password(result)
.realName(playerName)
.registrationDate(System.currentTimeMillis())
.build();
return dataSource.saveAuth(auth);
}
/**
* Force a player to login, i.e. the player is logged in without needing his password.
*
* @param player The player to log in
*/
public void forceLogin(Player player) {
management.forceLogin(player);
}
/**
* Force a player to logout.
*
* @param player The player to log out
*/
public void forceLogout(Player player) {
management.performLogout(player);
}
/**
* Force an ONLINE player to register.
*
* @param player The player to register
* @param password The password to use
* @param autoLogin Should the player be authenticated automatically after the registration?
*/
public void forceRegister(Player player, String password, boolean autoLogin) {
management.performRegister(RegistrationMethod.API_REGISTRATION,
ApiPasswordRegisterParams.of(player, password, autoLogin));
}
/**
* Register an ONLINE player with the given password.
*
* @param player The player to register
* @param password The password to use
*/
public void forceRegister(Player player, String password) {
forceRegister(player, password, true);
}
/**
* Unregister a player from AuthMe.
*
* @param player The player to unregister
*/
public void forceUnregister(Player player) {
management.performUnregisterByAdmin(null, player.getName(), player);
}
/**
* Unregister a player from AuthMe by name.
*
* @param name the name of the player (case-insensitive)
*/
public void forceUnregister(String name) {
management.performUnregisterByAdmin(null, name, Bukkit.getPlayer(name));
}
/**
* Change a user's password
*
* @param name the user name
* @param newPassword the new password
*/
public void changePassword(String name, String newPassword) {
management.performPasswordChangeAsAdmin(null, name, newPassword);
}
/**
* Get all the registered names (lowercase)
*
* @return registered names
*/
public List<String> getRegisteredNames() {
List<String> registeredNames = new ArrayList<>();
dataSource.getAllAuths().forEach(auth -> registeredNames.add(auth.getNickname()));
return registeredNames;
}
/**
* Get all the registered real-names (original case)
*
* @return registered real-names
*/
public List<String> getRegisteredRealNames() {
List<String> registeredNames = new ArrayList<>();
dataSource.getAllAuths().forEach(auth -> registeredNames.add(auth.getRealName()));
return registeredNames;
}
/**
* Get the country code of the given IP address.
*
* @param ip textual IP address to lookup.
*
* @return two-character ISO 3166-1 alpha code for the country.
*/
public String getCountryCode(String ip) {
return geoIpService.getCountryCode(ip);
}
/**
* Get the country name of the given IP address.
*
* @param ip textual IP address to lookup.
*
* @return The name of the country.
*/
public String getCountryName(String ip) {
return geoIpService.getCountryName(ip);
}
}

View File

@ -0,0 +1,65 @@
package fr.xephi.authme.api.v3;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
/**
* Read-only player info exposed in the AuthMe API. The data in this object is copied from the
* database and not updated afterwards. As such, it may become outdated if the player data changes
* in AuthMe.
*
* @see AuthMeApi#getPlayerInfo
*/
public interface AuthMePlayer {
/**
* @return the case-sensitive name of the player, e.g. "thePlayer3030" - never null
*/
String getName();
/**
* Returns the UUID of the player as given by the server (may be offline UUID or not).
* The UUID is not present if AuthMe is configured not to store the UUID or if the data is not
* present (e.g. older record).
*
* @return player uuid, or empty optional if not available
*/
Optional<UUID> getUuid();
/**
* Returns the email address associated with this player, or an empty optional if not available.
*
* @return player's email or empty optional
*/
Optional<String> getEmail();
/**
* @return the registration date of the player's account - never null
*/
Instant getRegistrationDate();
/**
* Returns the IP address with which the player's account was registered. Returns an empty optional
* for older accounts, or if the account was registered by someone else (e.g. by an admin).
*
* @return the ip address used during the registration of the account, or empty optional
*/
Optional<String> getRegistrationIpAddress();
/**
* Returns the last login date of the player. An empty optional is returned if the player never logged in.
*
* @return date the player last logged in successfully, or empty optional if not applicable
*/
Optional<Instant> getLastLoginDate();
/**
* Returns the IP address the player last logged in with successfully. Returns an empty optional if the
* player never logged in.
*
* @return ip address the player last logged in with successfully, or empty optional if not applicable
*/
Optional<String> getLastLoginIpAddress();
}

View File

@ -0,0 +1,93 @@
package fr.xephi.authme.api.v3;
import fr.xephi.authme.data.auth.PlayerAuth;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
/**
* Implementation of {@link AuthMePlayer}. This implementation is not part of the API and
* may have breaking changes in subsequent releases.
*/
class AuthMePlayerImpl implements AuthMePlayer {
private String name;
private UUID uuid;
private String email;
private Instant registrationDate;
private String registrationIpAddress;
private Instant lastLoginDate;
private String lastLoginIpAddress;
AuthMePlayerImpl() {
}
/**
* Maps the given player auth to an AuthMePlayer instance. Returns an empty optional if
* the player auth is null.
*
* @param playerAuth the player auth or null
* @return the mapped player auth, or empty optional if the argument was null
*/
static Optional<AuthMePlayer> fromPlayerAuth(PlayerAuth playerAuth) {
if (playerAuth == null) {
return Optional.empty();
}
AuthMePlayerImpl authMeUser = new AuthMePlayerImpl();
authMeUser.name = playerAuth.getRealName();
authMeUser.uuid = playerAuth.getUuid();
authMeUser.email = nullIfDefault(playerAuth.getEmail(), PlayerAuth.DB_EMAIL_DEFAULT);
Long lastLoginMillis = nullIfDefault(playerAuth.getLastLogin(), PlayerAuth.DB_LAST_LOGIN_DEFAULT);
authMeUser.registrationDate = toInstant(playerAuth.getRegistrationDate());
authMeUser.registrationIpAddress = playerAuth.getRegistrationIp();
authMeUser.lastLoginDate = toInstant(lastLoginMillis);
authMeUser.lastLoginIpAddress = nullIfDefault(playerAuth.getLastIp(), PlayerAuth.DB_LAST_IP_DEFAULT);
return Optional.of(authMeUser);
}
@Override
public String getName() {
return name;
}
public Optional<UUID> getUuid() {
return Optional.ofNullable(uuid);
}
@Override
public Optional<String> getEmail() {
return Optional.ofNullable(email);
}
@Override
public Instant getRegistrationDate() {
return registrationDate;
}
@Override
public Optional<String> getRegistrationIpAddress() {
return Optional.ofNullable(registrationIpAddress);
}
@Override
public Optional<Instant> getLastLoginDate() {
return Optional.ofNullable( lastLoginDate);
}
@Override
public Optional<String> getLastLoginIpAddress() {
return Optional.ofNullable(lastLoginIpAddress);
}
private static Instant toInstant(Long epochMillis) {
return epochMillis == null ? null : Instant.ofEpochMilli(epochMillis);
}
private static <T> T nullIfDefault(T value, T defaultValue) {
return defaultValue.equals(value) ? null : value;
}
}

View File

@ -0,0 +1,61 @@
package fr.xephi.authme.command;
/**
* Wrapper for the description of a command argument.
*/
public class CommandArgumentDescription {
/**
* Argument name (one-word description of the argument).
*/
private final String name;
/**
* Argument description.
*/
private final String description;
/**
* Defines whether the argument is optional.
*/
private final boolean isOptional;
/**
* Constructor.
*
* @param name The argument name.
* @param description The argument description.
* @param isOptional True if the argument is optional, false otherwise.
*/
public CommandArgumentDescription(String name, String description, boolean isOptional) {
this.name = name;
this.description = description;
this.isOptional = isOptional;
}
/**
* Get the argument name.
*
* @return Argument name.
*/
public String getName() {
return this.name;
}
/**
* Get the argument description.
*
* @return Argument description.
*/
public String getDescription() {
return description;
}
/**
* Return whether the argument is optional.
*
* @return True if the argument is optional, false otherwise.
*/
public boolean isOptional() {
return isOptional;
}
}

View File

@ -0,0 +1,294 @@
package fr.xephi.authme.command;
import fr.xephi.authme.permission.PermissionNode;
import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Utils;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Arrays.asList;
/**
* Command description defines which labels ("names") will lead to a command and points to the
* {@link ExecutableCommand} implementation that executes the logic of the command.
* <p>
* CommandDescription instances are built hierarchically: they have one parent, or {@code null} for base commands
* (main commands such as {@code /authme}), and may have multiple children extending the mapping of the parent: e.g. if
* {@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 {
/**
* Defines the labels to execute the command. For example, if labels are "register" and "r" and the parent is
* the command for "/authme", then both "/authme register" and "/authme r" will be handled by this command.
*/
private List<String> labels;
/**
* Short description of the command.
*/
private String description;
/**
* Detailed description of what the command does.
*/
private String detailedDescription;
/**
* The class implementing the command described by this object.
*/
private Class<? extends ExecutableCommand> executableCommand;
/**
* The parent command.
*/
private CommandDescription parent;
/**
* The child commands that extend this command.
*/
private List<CommandDescription> children = new ArrayList<>();
/**
* The arguments the command takes.
*/
private List<CommandArgumentDescription> arguments;
/**
* Permission node required to execute this command.
*/
private PermissionNode permission;
/**
* Private constructor.
* <p>
* Note for developers: Instances should be created with {@link CommandBuilder#register()} to be properly
* registered in the command tree.
*
* @param labels command labels
* @param description description of the command
* @param detailedDescription detailed command description
* @param executableCommand class of the command implementation
* @param parent parent command
* @param arguments command arguments
* @param permission permission node required to execute this command
*/
private CommandDescription(List<String> labels, String description, String detailedDescription,
Class<? extends ExecutableCommand> executableCommand, CommandDescription parent,
List<CommandArgumentDescription> arguments, PermissionNode permission) {
this.labels = labels;
this.description = description;
this.detailedDescription = detailedDescription;
this.executableCommand = executableCommand;
this.parent = parent;
this.arguments = arguments;
this.permission = permission;
}
/**
* Return all relative labels of this command. For example, if this object describes {@code /authme register} and
* {@code /authme r}, then it will return a list with {@code register} and {@code r}. The parent label
* {@code authme} is not returned.
*
* @return All labels of the command description.
*/
public List<String> getLabels() {
return labels;
}
/**
* Check whether this command description has the given label.
*
* @param commandLabel The label to check for.
*
* @return {@code true} if this command contains the given label, {@code false} otherwise.
*/
public boolean hasLabel(String commandLabel) {
for (String label : labels) {
if (label.equalsIgnoreCase(commandLabel)) {
return true;
}
}
return false;
}
/**
* Return the {@link ExecutableCommand} class implementing this command.
*
* @return The executable command class
*/
public Class<? extends ExecutableCommand> getExecutableCommand() {
return executableCommand;
}
/**
* Return the parent.
*
* @return The parent command, or null for base commands
*/
public CommandDescription getParent() {
return parent;
}
/**
* Return the number of labels necessary to get to this command. This corresponds to the number of parents + 1.
*
* @return The number of labels, e.g. for "/authme abc def" the label count is 3
*/
public int getLabelCount() {
if (parent == null) {
return 1;
}
return parent.getLabelCount() + 1;
}
/**
* Return all command children.
*
* @return Command children.
*/
public List<CommandDescription> getChildren() {
return children;
}
/**
* Return all arguments the command takes.
*
* @return Command arguments.
*/
public List<CommandArgumentDescription> getArguments() {
return arguments;
}
/**
* Return a short description of the command.
*
* @return Command description.
*/
public String getDescription() {
return description;
}
/**
* Return a detailed description of the command.
*
* @return Detailed description.
*/
public String getDetailedDescription() {
return detailedDescription;
}
/**
* Return the permission node required to execute the command.
*
* @return The permission node, or null if none are required to execute the command.
*/
public PermissionNode getPermission() {
return permission;
}
/**
* Return a builder instance to create a new command description.
*
* @return The builder
*/
public static CommandBuilder builder() {
return new CommandBuilder();
}
/**
* Builder for initializing CommandDescription objects.
*/
public static final class CommandBuilder {
private List<String> labels;
private String description;
private String detailedDescription;
private Class<? extends ExecutableCommand> executableCommand;
private CommandDescription parent;
private List<CommandArgumentDescription> arguments = new ArrayList<>();
private PermissionNode permission;
/**
* Build a CommandDescription and register it onto the parent if available.
*
* @return The generated CommandDescription object
*/
public CommandDescription register() {
CommandDescription command = build();
if (command.parent != null) {
command.parent.children.add(command);
}
return command;
}
/**
* Build a CommandDescription (without registering it on the parent).
*
* @return The generated CommandDescription object
*/
public CommandDescription build() {
checkArgument(!Utils.isCollectionEmpty(labels), "Labels may not be empty");
checkArgument(!StringUtils.isBlank(description), "Description may not be empty");
checkArgument(!StringUtils.isBlank(detailedDescription), "Detailed description may not be empty");
checkArgument(executableCommand != null, "Executable command must be set");
// parents and permissions may be null; arguments may be empty
return new CommandDescription(labels, description, detailedDescription, executableCommand,
parent, arguments, permission);
}
public CommandBuilder labels(List<String> labels) {
this.labels = labels;
return this;
}
public CommandBuilder labels(String... labels) {
return labels(asList(labels));
}
public CommandBuilder description(String description) {
this.description = description;
return this;
}
public CommandBuilder detailedDescription(String detailedDescription) {
this.detailedDescription = detailedDescription;
return this;
}
public CommandBuilder executableCommand(Class<? extends ExecutableCommand> executableCommand) {
this.executableCommand = executableCommand;
return this;
}
public CommandBuilder parent(CommandDescription parent) {
this.parent = parent;
return this;
}
/**
* Add an argument that the command description requires. This method can be called multiples times to add
* multiple arguments.
*
* @param label The label of the argument (single word name of the argument)
* @param description The description of the argument
* @param isOptional True if the argument is optional, false if it is mandatory
*
* @return The builder
*/
public CommandBuilder withArgument(String label, String description, boolean isOptional) {
arguments.add(new CommandArgumentDescription(label, description, isOptional));
return this;
}
/**
* Add a permission node that a user must have to execute the command.
*
* @param permission The PermissionNode to add
* @return The builder
*/
public CommandBuilder permission(PermissionNode permission) {
this.permission = permission;
return this;
}
}
}

View File

@ -0,0 +1,186 @@
package fr.xephi.authme.command;
import ch.jalu.injector.factory.Factory;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.help.HelpProvider;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The AuthMe command handler, responsible for invoking the correct {@link ExecutableCommand} based on incoming
* command labels or for displaying a help message for unknown command labels.
*/
public class CommandHandler {
/**
* The threshold for suggesting a similar command. If the difference is below this value, we will
* ask the player whether he meant the similar command.
*/
private static final double SUGGEST_COMMAND_THRESHOLD = 0.75;
private final CommandMapper commandMapper;
private final PermissionsManager permissionsManager;
private final Messages messages;
private final HelpProvider helpProvider;
/**
* Map with ExecutableCommand children. The key is the type of the value.
*/
private Map<Class<? extends ExecutableCommand>, ExecutableCommand> commands = new HashMap<>();
@Inject
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(commandFactory, commandMapper.getCommandClasses());
}
/**
* Map a command that was invoked to the proper {@link CommandDescription} or return a useful error
* message upon failure.
*
* @param sender The command sender.
* @param bukkitCommandLabel The command label (Bukkit).
* @param bukkitArgs The command arguments (Bukkit).
*
* @return True if the command was executed, false otherwise.
*/
public boolean processCommand(CommandSender sender, String bukkitCommandLabel, String[] bukkitArgs) {
// Add the Bukkit command label to the front so we get a list like [authme, register, bobby, mysecret]
List<String> parts = skipEmptyArguments(bukkitArgs);
parts.add(0, bukkitCommandLabel);
FoundCommandResult result = commandMapper.mapPartsToCommand(sender, parts);
handleCommandResult(sender, result);
return !FoundResultStatus.MISSING_BASE_COMMAND.equals(result.getResultStatus());
}
/**
* Processes the given {@link FoundCommandResult} for the provided command sender.
*
* @param sender the command sender who executed the command
* @param result the command mapping result
*/
private void handleCommandResult(CommandSender sender, FoundCommandResult result) {
switch (result.getResultStatus()) {
case SUCCESS:
executeCommand(sender, result);
break;
case MISSING_BASE_COMMAND:
sender.sendMessage(ChatColor.DARK_RED + "Failed to parse " + AuthMe.getPluginName() + " command!");
break;
case INCORRECT_ARGUMENTS:
sendImproperArgumentsMessage(sender, result);
break;
case UNKNOWN_LABEL:
sendUnknownCommandMessage(sender, result);
break;
case NO_PERMISSION:
messages.send(sender, MessageKey.NO_PERMISSION);
break;
default:
throw new IllegalStateException("Unknown result status '" + result.getResultStatus() + "'");
}
}
/**
* Initialize all required ExecutableCommand objects.
*
* @param commandFactory factory to create command objects
* @param commandClasses the classes to instantiate
*/
private void initializeCommands(Factory<ExecutableCommand> commandFactory,
Set<Class<? extends ExecutableCommand>> commandClasses) {
for (Class<? extends ExecutableCommand> clazz : commandClasses) {
commands.put(clazz, commandFactory.newInstance(clazz));
}
}
/**
* Execute the command for the given command sender.
*
* @param sender The sender which initiated the command
* @param result The mapped result
*/
private void executeCommand(CommandSender sender, FoundCommandResult result) {
ExecutableCommand executableCommand = commands.get(result.getCommandDescription().getExecutableCommand());
List<String> arguments = result.getArguments();
executableCommand.executeCommand(sender, arguments);
}
/**
* Skip all entries of the given array that are simply whitespace.
*
* @param args The array to process
* @return List of the items that are not empty
*/
private static List<String> skipEmptyArguments(String[] args) {
List<String> cleanArguments = new ArrayList<>();
for (String argument : args) {
if (!StringUtils.isBlank(argument)) {
cleanArguments.add(argument);
}
}
return cleanArguments;
}
/**
* Show an "unknown command" message to the user and suggest an existing command if its similarity is within
* the defined threshold.
*
* @param sender The command sender
* @param result The command that was found during the mapping process
*/
private static void sendUnknownCommandMessage(CommandSender sender, FoundCommandResult result) {
sender.sendMessage(ChatColor.DARK_RED + "Unknown command!");
// Show a command suggestion if available and the difference isn't too big
if (result.getDifference() <= SUGGEST_COMMAND_THRESHOLD && result.getCommandDescription() != null) {
sender.sendMessage(ChatColor.YELLOW + "Did you mean " + ChatColor.GOLD
+ CommandUtils.constructCommandPath(result.getCommandDescription()) + ChatColor.YELLOW + "?");
}
sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + result.getLabels().get(0)
+ " help" + ChatColor.YELLOW + " to view help.");
}
private void sendImproperArgumentsMessage(CommandSender sender, FoundCommandResult result) {
CommandDescription command = result.getCommandDescription();
if (!permissionsManager.hasPermission(sender, command.getPermission())) {
messages.send(sender, MessageKey.NO_PERMISSION);
return;
}
ExecutableCommand executableCommand = commands.get(command.getExecutableCommand());
MessageKey usageMessage = executableCommand.getArgumentsMismatchMessage();
if (usageMessage == null) {
showHelpForCommand(sender, result);
} else {
messages.send(sender, usageMessage);
}
}
private void showHelpForCommand(CommandSender sender, FoundCommandResult result) {
sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!");
helpProvider.outputHelp(sender, result, HelpProvider.SHOW_ARGUMENTS);
List<String> labels = result.getLabels();
String childLabel = labels.size() >= 2 ? labels.get(1) : "";
sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE
+ "/" + labels.get(0) + " help " + childLabel);
}
}

View File

@ -0,0 +1,652 @@
package fr.xephi.authme.command;
import com.google.common.collect.ImmutableList;
import fr.xephi.authme.command.executable.HelpCommand;
import fr.xephi.authme.command.executable.authme.AccountsCommand;
import fr.xephi.authme.command.executable.authme.AuthMeCommand;
import fr.xephi.authme.command.executable.authme.BackupCommand;
import fr.xephi.authme.command.executable.authme.ChangePasswordAdminCommand;
import fr.xephi.authme.command.executable.authme.ConverterCommand;
import fr.xephi.authme.command.executable.authme.FirstSpawnCommand;
import fr.xephi.authme.command.executable.authme.ForceLoginCommand;
import fr.xephi.authme.command.executable.authme.GetEmailCommand;
import fr.xephi.authme.command.executable.authme.GetIpCommand;
import fr.xephi.authme.command.executable.authme.LastLoginCommand;
import fr.xephi.authme.command.executable.authme.PurgeBannedPlayersCommand;
import fr.xephi.authme.command.executable.authme.PurgeCommand;
import fr.xephi.authme.command.executable.authme.PurgeLastPositionCommand;
import fr.xephi.authme.command.executable.authme.PurgePlayerCommand;
import fr.xephi.authme.command.executable.authme.RecentPlayersCommand;
import fr.xephi.authme.command.executable.authme.RegisterAdminCommand;
import fr.xephi.authme.command.executable.authme.ReloadCommand;
import fr.xephi.authme.command.executable.authme.SetEmailCommand;
import fr.xephi.authme.command.executable.authme.SetFirstSpawnCommand;
import fr.xephi.authme.command.executable.authme.SetSpawnCommand;
import fr.xephi.authme.command.executable.authme.SpawnCommand;
import fr.xephi.authme.command.executable.authme.SwitchAntiBotCommand;
import fr.xephi.authme.command.executable.authme.TotpDisableAdminCommand;
import fr.xephi.authme.command.executable.authme.TotpViewStatusCommand;
import fr.xephi.authme.command.executable.authme.UnregisterAdminCommand;
import fr.xephi.authme.command.executable.authme.UpdateHelpMessagesCommand;
import fr.xephi.authme.command.executable.authme.VersionCommand;
import fr.xephi.authme.command.executable.authme.debug.DebugCommand;
import fr.xephi.authme.command.executable.captcha.CaptchaCommand;
import fr.xephi.authme.command.executable.changepassword.ChangePasswordCommand;
import fr.xephi.authme.command.executable.email.AddEmailCommand;
import fr.xephi.authme.command.executable.email.ChangeEmailCommand;
import fr.xephi.authme.command.executable.email.EmailBaseCommand;
import fr.xephi.authme.command.executable.email.EmailSetPasswordCommand;
import fr.xephi.authme.command.executable.email.ProcessCodeCommand;
import fr.xephi.authme.command.executable.email.RecoverEmailCommand;
import fr.xephi.authme.command.executable.email.ShowEmailCommand;
import fr.xephi.authme.command.executable.login.LoginCommand;
import fr.xephi.authme.command.executable.logout.LogoutCommand;
import fr.xephi.authme.command.executable.register.RegisterCommand;
import fr.xephi.authme.command.executable.totp.AddTotpCommand;
import fr.xephi.authme.command.executable.totp.ConfirmTotpCommand;
import fr.xephi.authme.command.executable.totp.RemoveTotpCommand;
import fr.xephi.authme.command.executable.totp.TotpBaseCommand;
import fr.xephi.authme.command.executable.totp.TotpCodeCommand;
import fr.xephi.authme.command.executable.unregister.UnregisterCommand;
import fr.xephi.authme.command.executable.verification.VerificationCommand;
import fr.xephi.authme.permission.AdminPermission;
import fr.xephi.authme.permission.DebugSectionPermissions;
import fr.xephi.authme.permission.PlayerPermission;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* Initializes all available AuthMe commands.
*/
public class CommandInitializer {
private static final boolean OPTIONAL = true;
private static final boolean MANDATORY = false;
private List<CommandDescription> commands;
public CommandInitializer() {
buildCommands();
}
/**
* Returns the description of all AuthMe commands.
*
* @return the command descriptions
*/
public List<CommandDescription> getCommands() {
return commands;
}
/**
* Builds the command description objects for all available AuthMe commands.
*/
private void buildCommands() {
// Register /authme and /email commands
CommandDescription authMeBase = buildAuthMeBaseCommand();
CommandDescription emailBase = buildEmailBaseCommand();
// Register the base login command
CommandDescription loginBase = CommandDescription.builder()
.parent(null)
.labels("login", "l", "log")
.description("Login command")
.detailedDescription("Command to log in using AuthMeReloaded.")
.withArgument("password", "Login password", MANDATORY)
.permission(PlayerPermission.LOGIN)
.executableCommand(LoginCommand.class)
.register();
// Register the base logout command
CommandDescription logoutBase = CommandDescription.builder()
.parent(null)
.labels("logout")
.description("Logout command")
.detailedDescription("Command to logout using AuthMeReloaded.")
.permission(PlayerPermission.LOGOUT)
.executableCommand(LogoutCommand.class)
.register();
// Register the base register command
CommandDescription registerBase = CommandDescription.builder()
.parent(null)
.labels("register", "reg")
.description("Register an account")
.detailedDescription("Command to register using AuthMeReloaded.")
.withArgument("password", "Password", OPTIONAL)
.withArgument("verifyPassword", "Verify password", OPTIONAL)
.permission(PlayerPermission.REGISTER)
.executableCommand(RegisterCommand.class)
.register();
// Register the base unregister command
CommandDescription unregisterBase = CommandDescription.builder()
.parent(null)
.labels("unregister", "unreg")
.description("Unregister an account")
.detailedDescription("Command to unregister using AuthMeReloaded.")
.withArgument("password", "Password", MANDATORY)
.permission(PlayerPermission.UNREGISTER)
.executableCommand(UnregisterCommand.class)
.register();
// Register the base changepassword command
CommandDescription changePasswordBase = CommandDescription.builder()
.parent(null)
.labels("changepassword", "changepass", "cp")
.description("Change password of an account")
.detailedDescription("Command to change your password using AuthMeReloaded.")
.withArgument("oldPassword", "Old password", MANDATORY)
.withArgument("newPassword", "New password", MANDATORY)
.permission(PlayerPermission.CHANGE_PASSWORD)
.executableCommand(ChangePasswordCommand.class)
.register();
// Create totp base command
CommandDescription totpBase = buildTotpBaseCommand();
// Register the base captcha command
CommandDescription captchaBase = CommandDescription.builder()
.parent(null)
.labels("captcha")
.description("Captcha command")
.detailedDescription("Captcha command for AuthMeReloaded.")
.withArgument("captcha", "The Captcha", MANDATORY)
.permission(PlayerPermission.CAPTCHA)
.executableCommand(CaptchaCommand.class)
.register();
// Register the base verification code command
CommandDescription verificationBase = CommandDescription.builder()
.parent(null)
.labels("verification")
.description("Verification command")
.detailedDescription("Command to complete the verification process for AuthMeReloaded.")
.withArgument("code", "The code", MANDATORY)
.permission(PlayerPermission.VERIFICATION_CODE)
.executableCommand(VerificationCommand.class)
.register();
List<CommandDescription> baseCommands = ImmutableList.of(authMeBase, emailBase, loginBase, logoutBase,
registerBase, unregisterBase, changePasswordBase, totpBase, captchaBase, verificationBase);
setHelpOnAllBases(baseCommands);
commands = baseCommands;
}
/**
* Creates a command description object for {@code /authme} including its children.
*
* @return the authme base command description
*/
private CommandDescription buildAuthMeBaseCommand() {
// Register the base AuthMe Reloaded command
CommandDescription authmeBase = CommandDescription.builder()
.labels("authme")
.description("AuthMe op commands")
.detailedDescription("The main AuthMeReloaded command. The root for all admin commands.")
.executableCommand(AuthMeCommand.class)
.register();
// Register the register command
CommandDescription.builder()
.parent(authmeBase)
.labels("register", "reg", "r")
.description("Register a player")
.detailedDescription("Register the specified player with the specified password.")
.withArgument("player", "Player name", MANDATORY)
.withArgument("password", "Password", MANDATORY)
.permission(AdminPermission.REGISTER)
.executableCommand(RegisterAdminCommand.class)
.register();
// Register the unregister command
CommandDescription.builder()
.parent(authmeBase)
.labels("unregister", "unreg", "unr")
.description("Unregister a player")
.detailedDescription("Unregister the specified player.")
.withArgument("player", "Player name", MANDATORY)
.permission(AdminPermission.UNREGISTER)
.executableCommand(UnregisterAdminCommand.class)
.register();
// Register the forcelogin command
CommandDescription.builder()
.parent(authmeBase)
.labels("forcelogin", "login")
.description("Enforce login player")
.detailedDescription("Enforce the specified player to login.")
.withArgument("player", "Online player name", OPTIONAL)
.permission(AdminPermission.FORCE_LOGIN)
.executableCommand(ForceLoginCommand.class)
.register();
// Register the changepassword command
CommandDescription.builder()
.parent(authmeBase)
.labels("password", "changepassword", "changepass", "cp")
.description("Change a player's password")
.detailedDescription("Change the password of a player.")
.withArgument("player", "Player name", MANDATORY)
.withArgument("pwd", "New password", MANDATORY)
.permission(AdminPermission.CHANGE_PASSWORD)
.executableCommand(ChangePasswordAdminCommand.class)
.register();
// Register the last login command
CommandDescription.builder()
.parent(authmeBase)
.labels("lastlogin", "ll")
.description("Player's last login")
.detailedDescription("View the date of the specified players last login.")
.withArgument("player", "Player name", OPTIONAL)
.permission(AdminPermission.LAST_LOGIN)
.executableCommand(LastLoginCommand.class)
.register();
// Register the accounts command
CommandDescription.builder()
.parent(authmeBase)
.labels("accounts", "account")
.description("Display player accounts")
.detailedDescription("Display all accounts of a player by his player name or IP.")
.withArgument("player", "Player name or IP", OPTIONAL)
.permission(AdminPermission.ACCOUNTS)
.executableCommand(AccountsCommand.class)
.register();
// Register the getemail command
CommandDescription.builder()
.parent(authmeBase)
.labels("email", "mail", "getemail", "getmail")
.description("Display player's email")
.detailedDescription("Display the email address of the specified player if set.")
.withArgument("player", "Player name", OPTIONAL)
.permission(AdminPermission.GET_EMAIL)
.executableCommand(GetEmailCommand.class)
.register();
// Register the setemail command
CommandDescription.builder()
.parent(authmeBase)
.labels("setemail", "setmail", "chgemail", "chgmail")
.description("Change player's email")
.detailedDescription("Change the email address of the specified player.")
.withArgument("player", "Player name", MANDATORY)
.withArgument("email", "Player email", MANDATORY)
.permission(AdminPermission.CHANGE_EMAIL)
.executableCommand(SetEmailCommand.class)
.register();
// Register the getip command
CommandDescription.builder()
.parent(authmeBase)
.labels("getip", "ip")
.description("Get player's IP")
.detailedDescription("Get the IP address of the specified online player.")
.withArgument("player", "Player name", MANDATORY)
.permission(AdminPermission.GET_IP)
.executableCommand(GetIpCommand.class)
.register();
// Register totp command
CommandDescription.builder()
.parent(authmeBase)
.labels("totp", "2fa")
.description("See if a player has enabled TOTP")
.detailedDescription("Returns whether the specified player has enabled two-factor authentication.")
.withArgument("player", "Player name", MANDATORY)
.permission(AdminPermission.VIEW_TOTP_STATUS)
.executableCommand(TotpViewStatusCommand.class)
.register();
// Register disable totp command
CommandDescription.builder()
.parent(authmeBase)
.labels("disabletotp", "disable2fa", "deletetotp", "delete2fa")
.description("Delete TOTP token of a player")
.detailedDescription("Disable two-factor authentication for a player.")
.withArgument("player", "Player name", MANDATORY)
.permission(AdminPermission.DISABLE_TOTP)
.executableCommand(TotpDisableAdminCommand.class)
.register();
// Register the spawn command
CommandDescription.builder()
.parent(authmeBase)
.labels("spawn", "home")
.description("Teleport to spawn")
.detailedDescription("Teleport to the spawn.")
.permission(AdminPermission.SPAWN)
.executableCommand(SpawnCommand.class)
.register();
// Register the setspawn command
CommandDescription.builder()
.parent(authmeBase)
.labels("setspawn", "chgspawn")
.description("Change the spawn")
.detailedDescription("Change the player's spawn to your current position.")
.permission(AdminPermission.SET_SPAWN)
.executableCommand(SetSpawnCommand.class)
.register();
// Register the firstspawn command
CommandDescription.builder()
.parent(authmeBase)
.labels("firstspawn", "firsthome")
.description("Teleport to first spawn")
.detailedDescription("Teleport to the first spawn.")
.permission(AdminPermission.FIRST_SPAWN)
.executableCommand(FirstSpawnCommand.class)
.register();
// Register the setfirstspawn command
CommandDescription.builder()
.parent(authmeBase)
.labels("setfirstspawn", "chgfirstspawn")
.description("Change the first spawn")
.detailedDescription("Change the first player's spawn to your current position.")
.permission(AdminPermission.SET_FIRST_SPAWN)
.executableCommand(SetFirstSpawnCommand.class)
.register();
// Register the purge command
CommandDescription.builder()
.parent(authmeBase)
.labels("purge", "delete")
.description("Purge old data")
.detailedDescription("Purge old AuthMeReloaded data longer than the specified number of days ago.")
.withArgument("days", "Number of days", MANDATORY)
.permission(AdminPermission.PURGE)
.executableCommand(PurgeCommand.class)
.register();
// Purge player command
CommandDescription.builder()
.parent(authmeBase)
.labels("purgeplayer")
.description("Purges the data of one player")
.detailedDescription("Purges data of the given player.")
.withArgument("player", "The player to purge", MANDATORY)
.withArgument("options", "'force' to run without checking if player is registered", OPTIONAL)
.permission(AdminPermission.PURGE_PLAYER)
.executableCommand(PurgePlayerCommand.class)
.register();
// Backup command
CommandDescription.builder()
.parent(authmeBase)
.labels("backup")
.description("Perform a backup")
.detailedDescription("Creates a backup of the registered users.")
.permission(AdminPermission.BACKUP)
.executableCommand(BackupCommand.class)
.register();
// Register the purgelastposition command
CommandDescription.builder()
.parent(authmeBase)
.labels("resetpos", "purgelastposition", "purgelastpos", "resetposition",
"resetlastposition", "resetlastpos")
.description("Purge player's last position")
.detailedDescription("Purge the last know position of the specified player or all of them.")
.withArgument("player/*", "Player name or * for all players", MANDATORY)
.permission(AdminPermission.PURGE_LAST_POSITION)
.executableCommand(PurgeLastPositionCommand.class)
.register();
// Register the purgebannedplayers command
CommandDescription.builder()
.parent(authmeBase)
.labels("purgebannedplayers", "purgebannedplayer", "deletebannedplayers", "deletebannedplayer")
.description("Purge banned players data")
.detailedDescription("Purge all AuthMeReloaded data for banned players.")
.permission(AdminPermission.PURGE_BANNED_PLAYERS)
.executableCommand(PurgeBannedPlayersCommand.class)
.register();
// Register the switchantibot command
CommandDescription.builder()
.parent(authmeBase)
.labels("switchantibot", "toggleantibot", "antibot")
.description("Switch AntiBot mode")
.detailedDescription("Switch or toggle the AntiBot mode to the specified state.")
.withArgument("mode", "ON / OFF", OPTIONAL)
.permission(AdminPermission.SWITCH_ANTIBOT)
.executableCommand(SwitchAntiBotCommand.class)
.register();
// Register the reload command
CommandDescription.builder()
.parent(authmeBase)
.labels("reload", "rld")
.description("Reload plugin")
.detailedDescription("Reload the AuthMeReloaded plugin.")
.permission(AdminPermission.RELOAD)
.executableCommand(ReloadCommand.class)
.register();
// Register the version command
CommandDescription.builder()
.parent(authmeBase)
.labels("version", "ver", "v", "about", "info")
.description("Version info")
.detailedDescription("Show detailed information about the installed AuthMeReloaded version, the "
+ "developers, contributors, and license.")
.executableCommand(VersionCommand.class)
.register();
CommandDescription.builder()
.parent(authmeBase)
.labels("converter", "convert", "conv")
.description("Converter command")
.detailedDescription("Converter command for AuthMeReloaded.")
.withArgument("job", "Conversion job: xauth / crazylogin / rakamak / "
+ "royalauth / vauth / sqliteToSql / mysqlToSqlite / loginsecurity", OPTIONAL)
.permission(AdminPermission.CONVERTER)
.executableCommand(ConverterCommand.class)
.register();
CommandDescription.builder()
.parent(authmeBase)
.labels("messages", "msg")
.description("Add missing help messages")
.detailedDescription("Adds missing texts to the current help messages file.")
.permission(AdminPermission.UPDATE_MESSAGES)
.executableCommand(UpdateHelpMessagesCommand.class)
.register();
CommandDescription.builder()
.parent(authmeBase)
.labels("recent")
.description("See players who have recently logged in")
.detailedDescription("Shows the last players that have logged in.")
.permission(AdminPermission.SEE_RECENT_PLAYERS)
.executableCommand(RecentPlayersCommand.class)
.register();
CommandDescription.builder()
.parent(authmeBase)
.labels("debug", "dbg")
.description("Debug features")
.detailedDescription("Allows various operations for debugging.")
.withArgument("child", "The child to execute", OPTIONAL)
.withArgument("arg", "argument (depends on debug section)", OPTIONAL)
.withArgument("arg", "argument (depends on debug section)", OPTIONAL)
.permission(DebugSectionPermissions.DEBUG_COMMAND)
.executableCommand(DebugCommand.class)
.register();
return authmeBase;
}
/**
* Creates a command description for {@code /email} including its children.
*
* @return the email base command description
*/
private CommandDescription buildEmailBaseCommand() {
// Register the base Email command
CommandDescription emailBase = CommandDescription.builder()
.parent(null)
.labels("email")
.description("Add email or recover password")
.detailedDescription("The AuthMeReloaded email command base.")
.executableCommand(EmailBaseCommand.class)
.register();
// Register the show command
CommandDescription.builder()
.parent(emailBase)
.labels("show", "myemail")
.description("Show Email")
.detailedDescription("Show your current email address.")
.permission(PlayerPermission.SEE_EMAIL)
.executableCommand(ShowEmailCommand.class)
.register();
// Register the add command
CommandDescription.builder()
.parent(emailBase)
.labels("add", "addemail", "addmail")
.description("Add Email")
.detailedDescription("Add a new email address to your account.")
.withArgument("email", "Email address", MANDATORY)
.withArgument("verifyEmail", "Email address verification", MANDATORY)
.permission(PlayerPermission.ADD_EMAIL)
.executableCommand(AddEmailCommand.class)
.register();
// Register the change command
CommandDescription.builder()
.parent(emailBase)
.labels("change", "changeemail", "changemail")
.description("Change Email")
.detailedDescription("Change an email address of your account.")
.withArgument("oldEmail", "Old email address", MANDATORY)
.withArgument("newEmail", "New email address", MANDATORY)
.permission(PlayerPermission.CHANGE_EMAIL)
.executableCommand(ChangeEmailCommand.class)
.register();
// Register the recover command
CommandDescription.builder()
.parent(emailBase)
.labels("recover", "recovery", "recoveremail", "recovermail")
.description("Recover password using email")
.detailedDescription("Recover your account using an Email address by sending a mail containing "
+ "a new password.")
.withArgument("email", "Email address", MANDATORY)
.permission(PlayerPermission.RECOVER_EMAIL)
.executableCommand(RecoverEmailCommand.class)
.register();
// Register the process recovery code command
CommandDescription.builder()
.parent(emailBase)
.labels("code")
.description("Submit code to recover password")
.detailedDescription("Recover your account by submitting a code delivered to your email.")
.withArgument("code", "Recovery code", MANDATORY)
.permission(PlayerPermission.RECOVER_EMAIL)
.executableCommand(ProcessCodeCommand.class)
.register();
// Register the change password after recovery command
CommandDescription.builder()
.parent(emailBase)
.labels("setpassword")
.description("Set new password after recovery")
.detailedDescription("Set a new password after successfully recovering your account.")
.withArgument("password", "New password", MANDATORY)
.permission(PlayerPermission.RECOVER_EMAIL)
.executableCommand(EmailSetPasswordCommand.class)
.register();
return emailBase;
}
/**
* Creates a command description object for {@code /totp} including its children.
*
* @return the totp base command description
*/
private CommandDescription buildTotpBaseCommand() {
// Register the base totp command
CommandDescription totpBase = CommandDescription.builder()
.parent(null)
.labels("totp", "2fa")
.description("TOTP commands")
.detailedDescription("Performs actions related to two-factor authentication.")
.executableCommand(TotpBaseCommand.class)
.register();
// Register the base totp code
CommandDescription.builder()
.parent(totpBase)
.labels("code", "c")
.description("Command for logging in")
.detailedDescription("Processes the two-factor authentication code during login.")
.withArgument("code", "The TOTP code to use to log in", MANDATORY)
.executableCommand(TotpCodeCommand.class)
.register();
// Register totp add
CommandDescription.builder()
.parent(totpBase)
.labels("add")
.description("Enables TOTP")
.detailedDescription("Enables two-factor authentication for your account.")
.permission(PlayerPermission.ENABLE_TWO_FACTOR_AUTH)
.executableCommand(AddTotpCommand.class)
.register();
// Register totp confirm
CommandDescription.builder()
.parent(totpBase)
.labels("confirm")
.description("Enables TOTP after successful code")
.detailedDescription("Saves the generated TOTP secret after confirmation.")
.withArgument("code", "Code from the given secret from /totp add", MANDATORY)
.permission(PlayerPermission.ENABLE_TWO_FACTOR_AUTH)
.executableCommand(ConfirmTotpCommand.class)
.register();
// Register totp remove
CommandDescription.builder()
.parent(totpBase)
.labels("remove")
.description("Removes TOTP")
.detailedDescription("Disables two-factor authentication for your account.")
.withArgument("code", "Current 2FA code", MANDATORY)
.permission(PlayerPermission.DISABLE_TWO_FACTOR_AUTH)
.executableCommand(RemoveTotpCommand.class)
.register();
return totpBase;
}
/**
* Sets the help command on all base commands, e.g. to register /authme help or /register help.
*
* @param commands the list of base commands to register a help child command on
*/
private void setHelpOnAllBases(Collection<CommandDescription> commands) {
final List<String> helpCommandLabels = Arrays.asList("help", "hlp", "h", "sos", "?");
for (CommandDescription base : commands) {
CommandDescription.builder()
.parent(base)
.labels(helpCommandLabels)
.description("View help")
.detailedDescription("View detailed help for /" + base.getLabels().get(0) + " commands.")
.withArgument("query", "The command or query to view help for.", OPTIONAL)
.executableCommand(HelpCommand.class)
.register();
}
}
}

View File

@ -0,0 +1,207 @@
package fr.xephi.authme.command;
import fr.xephi.authme.command.executable.HelpCommand;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Utils;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import static fr.xephi.authme.command.FoundResultStatus.INCORRECT_ARGUMENTS;
import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND;
import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL;
/**
* Maps incoming command parts to the correct {@link CommandDescription}.
*/
public class CommandMapper {
/**
* The class of the help command, to which the base label should also be passed in the arguments.
*/
private static final Class<? extends ExecutableCommand> HELP_COMMAND_CLASS = HelpCommand.class;
private final Collection<CommandDescription> baseCommands;
private final PermissionsManager permissionsManager;
@Inject
public CommandMapper(CommandInitializer commandInitializer, PermissionsManager permissionsManager) {
this.baseCommands = commandInitializer.getCommands();
this.permissionsManager = permissionsManager;
}
/**
* Map incoming command parts to a command. This processes all parts and distinguishes the labels from arguments.
*
* @param sender The command sender (null if none applicable)
* @param parts The parts to map to commands and arguments
* @return The generated {@link FoundCommandResult}
*/
public FoundCommandResult mapPartsToCommand(CommandSender sender, List<String> parts) {
if (Utils.isCollectionEmpty(parts)) {
return new FoundCommandResult(null, parts, null, 0.0, MISSING_BASE_COMMAND);
}
CommandDescription base = getBaseCommand(parts.get(0));
if (base == null) {
return new FoundCommandResult(null, parts, null, 0.0, MISSING_BASE_COMMAND);
}
// Prefer labels: /register help goes to "Help command", not "Register command" with argument 'help'
List<String> remainingParts = parts.subList(1, parts.size());
CommandDescription childCommand = getSuitableChild(base, remainingParts);
if (childCommand != null) {
FoundResultStatus status = getPermissionAwareStatus(sender, childCommand);
FoundCommandResult result = new FoundCommandResult(
childCommand, parts.subList(0, 2), parts.subList(2, parts.size()), 0.0, status);
return transformResultForHelp(result);
} else if (hasSuitableArgumentCount(base, remainingParts.size())) {
FoundResultStatus status = getPermissionAwareStatus(sender, base);
return new FoundCommandResult(base, parts.subList(0, 1), parts.subList(1, parts.size()), 0.0, status);
}
return getCommandWithSmallestDifference(base, parts);
}
/**
* Return all {@link ExecutableCommand} classes referenced in {@link CommandDescription} objects.
*
* @return all classes
* @see CommandInitializer#getCommands
*/
public Set<Class<? extends ExecutableCommand>> getCommandClasses() {
Set<Class<? extends ExecutableCommand>> classes = new HashSet<>(50);
for (CommandDescription command : baseCommands) {
classes.add(command.getExecutableCommand());
for (CommandDescription child : command.getChildren()) {
classes.add(child.getExecutableCommand());
}
}
return classes;
}
/**
* Return the command whose label matches the given parts the best. This method is called when
* a successful mapping could not be performed.
*
* @param base the base command
* @param parts the command parts
* @return the closest result
*/
private static FoundCommandResult getCommandWithSmallestDifference(CommandDescription base, List<String> parts) {
// Return the base command with incorrect arg count error if we only have one part
if (parts.size() <= 1) {
return new FoundCommandResult(base, parts, new ArrayList<>(), 0.0, INCORRECT_ARGUMENTS);
}
final String childLabel = parts.get(1);
double minDifference = Double.POSITIVE_INFINITY;
CommandDescription closestCommand = null;
for (CommandDescription child : base.getChildren()) {
double difference = getLabelDifference(child, childLabel);
if (difference < minDifference) {
minDifference = difference;
closestCommand = child;
}
}
// base command may have no children, in which case we return the base command with incorrect arguments error
if (closestCommand == null) {
return new FoundCommandResult(
base, parts.subList(0, 1), parts.subList(1, parts.size()), 0.0, INCORRECT_ARGUMENTS);
}
FoundResultStatus status = (minDifference == 0.0) ? INCORRECT_ARGUMENTS : UNKNOWN_LABEL;
final int partsSize = parts.size();
List<String> labels = parts.subList(0, Math.min(closestCommand.getLabelCount(), partsSize));
List<String> arguments = (labels.size() == partsSize)
? new ArrayList<>()
: parts.subList(labels.size(), partsSize);
return new FoundCommandResult(closestCommand, labels, arguments, minDifference, status);
}
private CommandDescription getBaseCommand(String label) {
String baseLabel = label.toLowerCase(Locale.ROOT);
if (baseLabel.startsWith("authme:")) {
baseLabel = baseLabel.substring("authme:".length());
}
for (CommandDescription command : baseCommands) {
if (command.hasLabel(baseLabel)) {
return command;
}
}
return null;
}
/**
* Return a child from a base command if the label and the argument count match.
*
* @param baseCommand The base command whose children should be checked
* @param parts The command parts received from the invocation; the first item is the potential label and any
* other items are command arguments. The first initial part that led to the base command should not
* be present.
*
* @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 (Utils.isCollectionEmpty(parts)) {
return null;
}
final String label = parts.get(0).toLowerCase(Locale.ROOT);
final int argumentCount = parts.size() - 1;
for (CommandDescription child : baseCommand.getChildren()) {
if (child.hasLabel(label) && hasSuitableArgumentCount(child, argumentCount)) {
return child;
}
}
return null;
}
private static FoundCommandResult transformResultForHelp(FoundCommandResult result) {
if (result.getCommandDescription() != null
&& HELP_COMMAND_CLASS == result.getCommandDescription().getExecutableCommand()) {
// For "/authme help register" we have labels = [authme, help] and arguments = [register]
// But for the help command we want labels = [authme, help] and arguments = [authme, register],
// so we can use the arguments as the labels to the command to show help for
List<String> arguments = new ArrayList<>(result.getArguments());
arguments.add(0, result.getLabels().get(0));
return new FoundCommandResult(result.getCommandDescription(), result.getLabels(),
arguments, result.getDifference(), result.getResultStatus());
}
return result;
}
private FoundResultStatus getPermissionAwareStatus(CommandSender sender, CommandDescription command) {
if (sender != null && !permissionsManager.hasPermission(sender, command.getPermission())) {
return FoundResultStatus.NO_PERMISSION;
}
return FoundResultStatus.SUCCESS;
}
private static boolean hasSuitableArgumentCount(CommandDescription command, int argumentCount) {
int minArgs = CommandUtils.getMinNumberOfArguments(command);
int maxArgs = CommandUtils.getMaxNumberOfArguments(command);
return argumentCount >= minArgs && argumentCount <= maxArgs;
}
private static double getLabelDifference(CommandDescription command, String givenLabel) {
return command.getLabels().stream()
.map(label -> StringUtils.getDifference(label, givenLabel))
.min(Double::compareTo)
.orElseThrow(() -> new IllegalStateException("Command does not have any labels set"));
}
}

View File

@ -0,0 +1,109 @@
package fr.xephi.authme.command;
import com.google.common.collect.Lists;
import org.bukkit.ChatColor;
import java.util.ArrayList;
import java.util.List;
/**
* Utility functions for {@link CommandDescription} objects.
*/
public final class CommandUtils {
private CommandUtils() {
}
/**
* Returns the minimum number of arguments required for running the command (= number of mandatory arguments).
*
* @param command the command to process
* @return min number of arguments required by the command
*/
public static int getMinNumberOfArguments(CommandDescription command) {
int mandatoryArguments = 0;
for (CommandArgumentDescription argument : command.getArguments()) {
if (!argument.isOptional()) {
++mandatoryArguments;
}
}
return mandatoryArguments;
}
/**
* Returns the maximum number of arguments the command accepts.
*
* @param command the command to process
* @return max number of arguments that may be passed to the command
*/
public static int getMaxNumberOfArguments(CommandDescription command) {
return command.getArguments().size();
}
/**
* Constructs a hierarchical list of commands for the given command. The commands are in order:
* the parents of the given command precede the provided command. For example, given the command
* for {@code /authme register}, a list with {@code [{authme}, {authme register}]} is returned.
*
* @param command the command to build a parent list for
* @return the parent list
*/
public static List<CommandDescription> constructParentList(CommandDescription command) {
List<CommandDescription> commands = new ArrayList<>();
CommandDescription currentCommand = command;
while (currentCommand != null) {
commands.add(currentCommand);
currentCommand = currentCommand.getParent();
}
return Lists.reverse(commands);
}
/**
* Returns a textual representation of the command, e.g. {@code /authme register}.
*
* @param command the command to create the path for
* @return the command string
*/
public static String constructCommandPath(CommandDescription command) {
StringBuilder sb = new StringBuilder();
String prefix = "/";
for (CommandDescription ancestor : constructParentList(command)) {
sb.append(prefix).append(ancestor.getLabels().get(0));
prefix = " ";
}
return sb.toString();
}
/**
* Constructs a command path with color formatting, based on the supplied labels. This includes
* the command's arguments, as defined in the provided command description. The list of labels
* must contain all labels to be used.
*
* @param command the command to read arguments from
* @param correctLabels the labels to use (must be complete)
* @return formatted command syntax incl. arguments
*/
public static String buildSyntax(CommandDescription command, List<String> correctLabels) {
String commandSyntax = ChatColor.WHITE + "/" + correctLabels.get(0) + ChatColor.YELLOW;
for (int i = 1; i < correctLabels.size(); ++i) {
commandSyntax += " " + correctLabels.get(i);
}
for (CommandArgumentDescription argument : command.getArguments()) {
commandSyntax += " " + formatArgument(argument);
}
return commandSyntax;
}
/**
* Formats a command argument with the proper type of brackets.
*
* @param argument the argument to format
* @return the formatted argument
*/
public static String formatArgument(CommandArgumentDescription argument) {
if (argument.isOptional()) {
return "[" + argument.getName() + "]";
}
return "<" + argument.getName() + ">";
}
}

View File

@ -0,0 +1,31 @@
package fr.xephi.authme.command;
import fr.xephi.authme.message.MessageKey;
import org.bukkit.command.CommandSender;
import java.util.List;
/**
* Base class for AuthMe commands that can be executed.
*/
public interface ExecutableCommand {
/**
* Executes the command with the given arguments.
*
* @param sender the command sender (initiator of the command)
* @param arguments the arguments
*/
void executeCommand(CommandSender sender, List<String> arguments);
/**
* Returns the message to show to the user if the command is used with the wrong arguments.
* If null is returned, the standard help (/<i>command</i> help) output is shown.
*
* @return the message explaining the command's usage, or {@code null} for default behavior
*/
default MessageKey getArgumentsMismatchMessage() {
return null;
}
}

View File

@ -0,0 +1,79 @@
package fr.xephi.authme.command;
import java.util.List;
/**
* Result of a command mapping by {@link CommandHandler}. An object of this class represents a successful mapping
* as well as erroneous ones, as communicated with {@link FoundResultStatus}.
* <p>
* Fields other than {@link FoundResultStatus} are available depending, among other factors, on the status:
* <ul>
* <li>{@link FoundResultStatus#SUCCESS} entails that mapping the input to a command was successful. Therefore,
* the command description, labels and arguments are set. The difference is 0.0.</li>
* <li>{@link FoundResultStatus#INCORRECT_ARGUMENTS}: The received parts could be mapped to a command but the argument
* count doesn't match. Guarantees that the command description field is not null; difference is 0.0</li>
* <li>{@link FoundResultStatus#UNKNOWN_LABEL}: The labels could not be mapped to a command. The command description
* may be set to the most similar command, or it may be null. Difference is above 0.0.</li>
* <li>{@link FoundResultStatus#NO_PERMISSION}: The command could be matched properly but the sender does not have
* permission to execute it.</li>
* <li>{@link FoundResultStatus#MISSING_BASE_COMMAND} should never occur. All other fields may be null and any further
* processing of the object should be aborted.</li>
* </ul>
*/
public class FoundCommandResult {
/**
* The command description instance.
*/
private final CommandDescription commandDescription;
/**
* The labels used to invoke the command. This may be different for the same {@link ExecutableCommand} instance
* if multiple labels have been defined, e.g. "/authme register" and "/authme reg".
*/
private final List<String> labels;
/** The command arguments. */
private final List<String> arguments;
/** The difference between the matched command and the supplied labels. */
private final double difference;
/** The status of the result (see class description). */
private final FoundResultStatus resultStatus;
/**
* Constructor.
*
* @param commandDescription The command description.
* @param labels The labels used to access the command.
* @param arguments The command arguments.
* @param difference The difference between the supplied labels and the matched command.
* @param resultStatus The status of the result.
*/
public FoundCommandResult(CommandDescription commandDescription, List<String> labels, List<String> arguments,
double difference, FoundResultStatus resultStatus) {
this.commandDescription = commandDescription;
this.labels = labels;
this.arguments = arguments;
this.difference = difference;
this.resultStatus = resultStatus;
}
public CommandDescription getCommandDescription() {
return this.commandDescription;
}
public List<String> getArguments() {
return this.arguments;
}
public List<String> getLabels() {
return this.labels;
}
public double getDifference() {
return difference;
}
public FoundResultStatus getResultStatus() {
return resultStatus;
}
}

View File

@ -0,0 +1,18 @@
package fr.xephi.authme.command;
/**
* Result status for mapping command parts. See {@link FoundCommandResult} for a detailed description of the states.
*/
public enum FoundResultStatus {
SUCCESS,
INCORRECT_ARGUMENTS,
UNKNOWN_LABEL,
NO_PERMISSION,
MISSING_BASE_COMMAND
}

View File

@ -0,0 +1,45 @@
package fr.xephi.authme.command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
/**
* Common base type for player-only commands, handling the verification that the command sender is indeed a player.
*/
public abstract class PlayerCommand implements ExecutableCommand {
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
if (sender instanceof Player) {
runCommand((Player) sender, arguments);
} else {
String alternative = getAlternativeCommand();
if (alternative != null) {
sender.sendMessage("此命令仅限玩家使用!请使用 " + alternative + " .");
} else {
sender.sendMessage("此命令只能被玩家执行.");
}
}
}
/**
* Runs the command with the given player and arguments.
*
* @param player the player who initiated the command
* @param arguments the arguments supplied with the command
*/
protected abstract void runCommand(Player player, List<String> arguments);
/**
* Returns an alternative command (textual representation) that is not restricted to players only.
* Example: {@code "/authme register <playerName> <password>"}
*
* @return Alternative command not restricted to players, or null if not applicable
*/
protected String getAlternativeCommand() {
return null;
}
}

View File

@ -0,0 +1,64 @@
package fr.xephi.authme.command.executable;
import fr.xephi.authme.command.CommandMapper;
import fr.xephi.authme.command.CommandUtils;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.command.FoundCommandResult;
import fr.xephi.authme.command.FoundResultStatus;
import fr.xephi.authme.command.help.HelpProvider;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND;
import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL;
import static fr.xephi.authme.command.help.HelpProvider.ALL_OPTIONS;
import static fr.xephi.authme.command.help.HelpProvider.SHOW_ALTERNATIVES;
import static fr.xephi.authme.command.help.HelpProvider.SHOW_CHILDREN;
import static fr.xephi.authme.command.help.HelpProvider.SHOW_COMMAND;
import static fr.xephi.authme.command.help.HelpProvider.SHOW_DESCRIPTION;
/**
* Displays help information to a user.
*/
public class HelpCommand implements ExecutableCommand {
@Inject
private CommandMapper commandMapper;
@Inject
private HelpProvider helpProvider;
// Convention: arguments is not the actual invoked arguments but the command that was invoked,
// e.g. "/authme help register" would typically be arguments = [register], but here we pass [authme, register]
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
FoundCommandResult result = commandMapper.mapPartsToCommand(sender, arguments);
FoundResultStatus resultStatus = result.getResultStatus();
if (MISSING_BASE_COMMAND.equals(resultStatus)) {
sender.sendMessage(ChatColor.DARK_RED + "Could not get base command");
return;
} else if (UNKNOWN_LABEL.equals(resultStatus)) {
if (result.getCommandDescription() == null) {
sender.sendMessage(ChatColor.DARK_RED + "Unknown command");
return;
} else {
sender.sendMessage(ChatColor.GOLD + "Assuming " + ChatColor.WHITE
+ CommandUtils.constructCommandPath(result.getCommandDescription()));
}
}
int mappedCommandLevel = result.getCommandDescription().getLabelCount();
if (mappedCommandLevel == 1) {
helpProvider.outputHelp(sender, result,
SHOW_COMMAND | SHOW_DESCRIPTION | SHOW_CHILDREN | SHOW_ALTERNATIVES);
} else {
helpProvider.outputHelp(sender, result, ALL_OPTIONS);
}
}
}

View File

@ -0,0 +1,74 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
import java.util.Locale;
/**
* Shows all accounts registered by the same IP address for the given player name or IP address.
*/
public class AccountsCommand implements ExecutableCommand {
@Inject
private DataSource dataSource;
@Inject
private BukkitService bukkitService;
@Inject
private CommonService commonService;
@Override
public void executeCommand(final CommandSender sender, List<String> arguments) {
// TODO #1366: last IP vs. registration IP?
final String playerName = arguments.isEmpty() ? sender.getName() : arguments.get(0);
// Assumption: a player name cannot contain '.'
if (playerName.contains(".")) {
bukkitService.runTaskAsynchronously(() -> {
List<String> accountList = dataSource.getAllAuthsByIp(playerName);
if (accountList.isEmpty()) {
sender.sendMessage("[AuthMe] This IP does not exist in the database.");
} else if (accountList.size() == 1) {
sender.sendMessage("[AuthMe] " + playerName + " is a single account player");
} else {
outputAccountsList(sender, playerName, accountList);
}
});
} else {
bukkitService.runTaskAsynchronously(() -> {
PlayerAuth auth = dataSource.getAuth(playerName.toLowerCase(Locale.ROOT));
if (auth == null) {
commonService.send(sender, MessageKey.UNKNOWN_USER);
return;
} else if (auth.getLastIp() == null) {
sender.sendMessage("No known last IP address for player");
return;
}
List<String> accountList = dataSource.getAllAuthsByIp(auth.getLastIp());
if (accountList.isEmpty()) {
commonService.send(sender, MessageKey.UNKNOWN_USER);
} else if (accountList.size() == 1) {
sender.sendMessage("[AuthMe] " + playerName + " is a single account player");
} else {
outputAccountsList(sender, playerName, accountList);
}
});
}
}
private static void outputAccountsList(CommandSender sender, String playerName, List<String> accountList) {
sender.sendMessage("[AuthMe] " + playerName + " has " + accountList.size() + " accounts.");
String message = "[AuthMe] " + String.join(", ", accountList) + ".";
sender.sendMessage(message);
}
}

View File

@ -0,0 +1,24 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.ExecutableCommand;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import java.util.List;
/**
* AuthMe base command; shows the version and some command pointers.
*/
public class AuthMeCommand implements ExecutableCommand {
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
sender.sendMessage(ChatColor.GREEN + "This server is running " + AuthMe.getPluginName() + " v"
+ AuthMe.getPluginVersion() + " b" + AuthMe.getPluginBuildNumber()+ "! " + ChatColor.RED + "<3");
sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/authme help" + ChatColor.YELLOW
+ " to view help.");
sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/authme about" + ChatColor.YELLOW
+ " to view about.");
}
}

View File

@ -0,0 +1,23 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.service.BackupService;
import fr.xephi.authme.service.BackupService.BackupCause;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
/**
* Command to perform a backup.
*/
public class BackupCommand implements ExecutableCommand {
@Inject
private BackupService backupService;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
backupService.doBackup(BackupCause.COMMAND, sender);
}
}

View File

@ -0,0 +1,41 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.service.ValidationService.ValidationResult;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
/**
* Admin command for changing a player's password.
*/
public class ChangePasswordAdminCommand implements ExecutableCommand {
@Inject
private ValidationService validationService;
@Inject
private CommonService commonService;
@Inject
private Management management;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
// Get the player and password
final String playerName = arguments.get(0);
final String playerPass = arguments.get(1);
// Validate the password
ValidationResult validationResult = validationService.validatePassword(playerPass, playerName);
if (validationResult.hasError()) {
commonService.send(sender, validationResult.getMessageKey(), validationResult.getArgs());
} else {
management.performPasswordChangeAsAdmin(sender, playerName, playerPass);
}
}
}

View File

@ -0,0 +1,95 @@
package fr.xephi.authme.command.executable.authme;
import ch.jalu.injector.factory.Factory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSortedMap;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.datasource.converter.Converter;
import fr.xephi.authme.datasource.converter.CrazyLoginConverter;
import fr.xephi.authme.datasource.converter.LoginSecurityConverter;
import fr.xephi.authme.datasource.converter.MySqlToSqlite;
import fr.xephi.authme.datasource.converter.RakamakConverter;
import fr.xephi.authme.datasource.converter.RoyalAuthConverter;
import fr.xephi.authme.datasource.converter.SqliteToSql;
import fr.xephi.authme.datasource.converter.VAuthConverter;
import fr.xephi.authme.datasource.converter.XAuthConverter;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Converter command: launches conversion based on its parameters.
*/
public class ConverterCommand implements ExecutableCommand {
@VisibleForTesting
static final Map<String, Class<? extends Converter>> CONVERTERS = getConverters();
private final ConsoleLogger logger = ConsoleLoggerFactory.get(ConverterCommand.class);
@Inject
private CommonService commonService;
@Inject
private BukkitService bukkitService;
@Inject
private Factory<Converter> converterFactory;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
Class<? extends Converter> converterClass = getConverterClassFromArgs(arguments);
if (converterClass == null) {
sender.sendMessage("Converters: " + String.join(", ", CONVERTERS.keySet()));
return;
}
// Get the proper converter instance
final Converter converter = converterFactory.newInstance(converterClass);
// Run the convert job
bukkitService.runTaskAsynchronously(() -> {
try {
converter.execute(sender);
} catch (Exception e) {
commonService.send(sender, MessageKey.ERROR);
logger.logException("Error during conversion:", e);
}
});
// Show a status message
sender.sendMessage("[AuthMe] Successfully started " + arguments.get(0));
}
private static Class<? extends Converter> getConverterClassFromArgs(List<String> arguments) {
return arguments.isEmpty()
? null
: CONVERTERS.get(arguments.get(0).toLowerCase(Locale.ROOT));
}
/**
* Initializes a map with all available converters.
*
* @return map with all available converters
*/
private static Map<String, Class<? extends Converter>> getConverters() {
return ImmutableSortedMap.<String, Class<? extends Converter>>naturalOrder()
.put("xauth", XAuthConverter.class)
.put("crazylogin", CrazyLoginConverter.class)
.put("rakamak", RakamakConverter.class)
.put("royalauth", RoyalAuthConverter.class)
.put("vauth", VAuthConverter.class)
.put("sqlitetosql", SqliteToSql.class)
.put("mysqltosqlite", MySqlToSqlite.class)
.put("loginsecurity", LoginSecurityConverter.class)
.build();
}
}

View File

@ -0,0 +1,36 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.SpawnLoader;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.TeleportUtils;
import org.bukkit.entity.Player;
import fr.xephi.authme.AuthMe;
import javax.inject.Inject;
import java.util.List;
/**
* Teleports the player to the first spawn.
*/
public class FirstSpawnCommand extends PlayerCommand {
@Inject
private Settings settings;
@Inject
private SpawnLoader spawnLoader;
@Override
public void runCommand(Player player, List<String> arguments) {
if (spawnLoader.getFirstSpawn() == null) {
player.sendMessage("[AuthMe] First spawn has failed, please try to define the first spawn");
} else {
//String name= player.getName();
if(settings.getProperty(SecuritySettings.SMART_ASYNC_TELEPORT)) {
TeleportUtils.teleport(player, spawnLoader.getFirstSpawn());
} else {
player.teleport(spawnLoader.getFirstSpawn());
}
//player.teleport(spawnLoader.getFirstSpawn());
}
}
}

View File

@ -0,0 +1,44 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.service.BukkitService;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
import static fr.xephi.authme.permission.PlayerPermission.CAN_LOGIN_BE_FORCED;
/**
* Forces the login of a player, i.e. logs the player in without the need of a (correct) password.
*/
public class ForceLoginCommand implements ExecutableCommand {
@Inject
private PermissionsManager permissionsManager;
@Inject
private Management management;
@Inject
private BukkitService bukkitService;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
// Get the player query
String playerName = arguments.isEmpty() ? sender.getName() : arguments.get(0);
Player player = bukkitService.getPlayerExact(playerName);
if (player == null || !player.isOnline()) {
sender.sendMessage("Player needs to be online!");
} else if (!permissionsManager.hasPermission(player, CAN_LOGIN_BE_FORCED)) {
sender.sendMessage("You cannot force login the player " + playerName + "!");
} else {
management.forceLogin(player);
sender.sendMessage("Force login for " + playerName + " performed!");
}
}
}

View File

@ -0,0 +1,35 @@
package fr.xephi.authme.command.executable.authme;
import ch.jalu.datasourcecolumns.data.DataSourceValue;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
/**
* Returns a player's email.
*/
public class GetEmailCommand implements ExecutableCommand {
@Inject
private DataSource dataSource;
@Inject
private CommonService commonService;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
String playerName = arguments.isEmpty() ? sender.getName() : arguments.get(0);
DataSourceValue<String> email = dataSource.getEmail(playerName);
if (email.rowExists()) {
sender.sendMessage("[AuthMe] " + playerName + "'s email: " + email.getValue());
} else {
commonService.send(sender, MessageKey.UNKNOWN_USER);
}
}
}

View File

@ -0,0 +1,41 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
public class GetIpCommand implements ExecutableCommand {
@Inject
private BukkitService bukkitService;
@Inject
private DataSource dataSource;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
String playerName = arguments.get(0);
Player player = bukkitService.getPlayerExact(playerName);
PlayerAuth auth = dataSource.getAuth(playerName);
if (player != null) {
sender.sendMessage("Current IP of " + player.getName() + " is " + PlayerUtils.getPlayerIp(player)
+ ":" + player.getAddress().getPort());
}
if (auth == null) {
String displayName = player == null ? playerName : player.getName();
sender.sendMessage(displayName + " is not registered in the database");
} else {
sender.sendMessage("Database: last IP: " + auth.getLastIp() + ", registration IP: "
+ auth.getRegistrationIp());
}
}
}

View File

@ -0,0 +1,56 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.Date;
import java.util.List;
/**
* Returns the last login date of the given user.
*/
public class LastLoginCommand implements ExecutableCommand {
@Inject
private DataSource dataSource;
@Inject
private CommonService commonService;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
// Get the player
String playerName = arguments.isEmpty() ? sender.getName() : arguments.get(0);
PlayerAuth auth = dataSource.getAuth(playerName);
if (auth == null) {
commonService.send(sender, MessageKey.UNKNOWN_USER);
return;
}
// Get the last login date
final Long lastLogin = auth.getLastLogin();
final String lastLoginDate = lastLogin == null ? "never" : new Date(lastLogin).toString();
// Show the player status
sender.sendMessage("[AuthMe] " + playerName + " last login: " + lastLoginDate);
if (lastLogin != null) {
sender.sendMessage("[AuthMe] The player " + playerName + " last logged in "
+ createLastLoginIntervalMessage(lastLogin) + " ago");
}
sender.sendMessage("[AuthMe] Last player's IP: " + auth.getLastIp());
}
private static String createLastLoginIntervalMessage(long lastLogin) {
final long diff = System.currentTimeMillis() - lastLogin;
return (int) (diff / 86400000) + " days "
+ (int) (diff / 3600000 % 24) + " hours "
+ (int) (diff / 60000 % 60) + " mins "
+ (int) (diff / 1000 % 60) + " secs";
}
}

View File

@ -0,0 +1,38 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.task.purge.PurgeService;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/**
* Command for purging data of banned players. Depending on the settings
* it purges (deletes) data from third-party plugins as well.
*/
public class PurgeBannedPlayersCommand implements ExecutableCommand {
@Inject
private PurgeService purgeService;
@Inject
private BukkitService bukkitService;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
// Get the list of banned players
Set<OfflinePlayer> bannedPlayers = bukkitService.getBannedPlayers();
Set<String> namedBanned = new HashSet<>(bannedPlayers.size());
for (OfflinePlayer offlinePlayer : bannedPlayers) {
namedBanned.add(offlinePlayer.getName().toLowerCase(Locale.ROOT));
}
purgeService.purgePlayers(sender, namedBanned, bannedPlayers.toArray(new OfflinePlayer[bannedPlayers.size()]));
}
}

View File

@ -0,0 +1,51 @@
package fr.xephi.authme.command.executable.authme;
import com.google.common.primitives.Ints;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.task.purge.PurgeService;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.Calendar;
import java.util.List;
/**
* Command for purging the data of players which have not been online for a given number
* of days. Depending on the settings, this removes player data in third-party plugins as well.
*/
public class PurgeCommand implements ExecutableCommand {
private static final int MINIMUM_LAST_SEEN_DAYS = 30;
@Inject
private PurgeService purgeService;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
// Get the days parameter
String daysStr = arguments.get(0);
// Convert the days string to an integer value, and make sure it's valid
Integer days = Ints.tryParse(daysStr);
if (days == null) {
sender.sendMessage(ChatColor.RED + "The value you've entered is invalid!");
return;
}
// Validate the value
if (days < MINIMUM_LAST_SEEN_DAYS) {
sender.sendMessage(ChatColor.RED + "You can only purge data older than "
+ MINIMUM_LAST_SEEN_DAYS + " days");
return;
}
// Create a calender instance to determine the date
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, -days);
long until = calendar.getTimeInMillis();
// Run the purge
purgeService.runPurge(sender, until);
}
}

View File

@ -0,0 +1,56 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
/**
* Removes the stored last position of a user or of all.
*/
public class PurgeLastPositionCommand implements ExecutableCommand {
@Inject
private DataSource dataSource;
@Inject
private CommonService commonService;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
String playerName = arguments.isEmpty() ? sender.getName() : arguments.get(0);
if ("*".equals(playerName)) {
for (PlayerAuth auth : dataSource.getAllAuths()) {
resetLastPosition(auth);
dataSource.updateQuitLoc(auth);
// TODO: send an update when a messaging service will be implemented (QUITLOC)
}
sender.sendMessage("All players last position locations are now reset");
} else {
// Get the user auth and make sure the user exists
PlayerAuth auth = dataSource.getAuth(playerName);
if (auth == null) {
commonService.send(sender, MessageKey.UNKNOWN_USER);
return;
}
resetLastPosition(auth);
dataSource.updateQuitLoc(auth);
// TODO: send an update when a messaging service will be implemented (QUITLOC)
sender.sendMessage(playerName + "'s last position location is now reset");
}
}
private static void resetLastPosition(PlayerAuth auth) {
auth.setQuitLocX(0d);
auth.setQuitLocY(0d);
auth.setQuitLocZ(0d);
auth.setWorld("world");
}
}

View File

@ -0,0 +1,47 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.task.purge.PurgeExecutor;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
import java.util.Locale;
import static java.util.Collections.singletonList;
/**
* Command to purge a player.
*/
public class PurgePlayerCommand implements ExecutableCommand {
@Inject
private PurgeExecutor purgeExecutor;
@Inject
private BukkitService bukkitService;
@Inject
private DataSource dataSource;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
String option = arguments.size() > 1 ? arguments.get(1) : null;
bukkitService.runTaskAsynchronously(
() -> executeCommand(sender, arguments.get(0), option));
}
private void executeCommand(CommandSender sender, String name, String option) {
if ("force".equals(option) || !dataSource.isAuthAvailable(name)) {
OfflinePlayer offlinePlayer = bukkitService.getOfflinePlayer(name);
purgeExecutor.executePurge(singletonList(offlinePlayer), singletonList(name.toLowerCase(Locale.ROOT)));
sender.sendMessage("Purged data for player " + name);
} else {
sender.sendMessage("This player is still registered! Are you sure you want to proceed? "
+ "Use '/authme purgeplayer " + name + " force' to run the command anyway");
}
}
}

View File

@ -0,0 +1,55 @@
package fr.xephi.authme.command.executable.authme;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import static java.time.Instant.ofEpochMilli;
/**
* Command showing the most recent logged in players.
*/
public class RecentPlayersCommand implements ExecutableCommand {
/** DateTime formatter, producing Strings such as "10:42 AM, 11 Jul". */
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("hh:mm a, dd MMM");
@Inject
private DataSource dataSource;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
List<PlayerAuth> recentPlayers = dataSource.getRecentlyLoggedInPlayers();
sender.sendMessage(ChatColor.BLUE + "[AuthMe] Recently logged in players");
for (PlayerAuth auth : recentPlayers) {
sender.sendMessage(formatPlayerMessage(auth));
}
}
@VisibleForTesting
ZoneId getZoneId() {
return ZoneId.systemDefault();
}
private String formatPlayerMessage(PlayerAuth auth) {
String lastLoginText;
if (auth.getLastLogin() == null) {
lastLoginText = "never";
} else {
LocalDateTime lastLogin = LocalDateTime.ofInstant(ofEpochMilli(auth.getLastLogin()), getZoneId());
lastLoginText = DATE_FORMAT.format(lastLogin);
}
return "- " + auth.getRealName() + " (" + lastLoginText + " with IP " + auth.getLastIp() + ")";
}
}

View File

@ -0,0 +1,85 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.security.PasswordSecurity;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.service.ValidationService.ValidationResult;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
import java.util.Locale;
/**
* Admin command to register a user.
*/
public class RegisterAdminCommand implements ExecutableCommand {
private final ConsoleLogger logger = ConsoleLoggerFactory.get(RegisterAdminCommand.class);
@Inject
private PasswordSecurity passwordSecurity;
@Inject
private CommonService commonService;
@Inject
private DataSource dataSource;
@Inject
private BukkitService bukkitService;
@Inject
private ValidationService validationService;
@Override
public void executeCommand(final CommandSender sender, List<String> arguments) {
// Get the player name and password
final String playerName = arguments.get(0);
final String playerPass = arguments.get(1);
final String playerNameLowerCase = playerName.toLowerCase(Locale.ROOT);
// Command logic
ValidationResult passwordValidation = validationService.validatePassword(playerPass, playerName);
if (passwordValidation.hasError()) {
commonService.send(sender, passwordValidation.getMessageKey(), passwordValidation.getArgs());
return;
}
bukkitService.runTaskOptionallyAsync(() -> {
if (dataSource.isAuthAvailable(playerNameLowerCase)) {
commonService.send(sender, MessageKey.NAME_ALREADY_REGISTERED);
return;
}
HashedPassword hashedPassword = passwordSecurity.computeHash(playerPass, playerNameLowerCase);
PlayerAuth auth = PlayerAuth.builder()
.name(playerNameLowerCase)
.realName(playerName)
.password(hashedPassword)
.registrationDate(System.currentTimeMillis())
.build();
if (!dataSource.saveAuth(auth)) {
commonService.send(sender, MessageKey.ERROR);
return;
}
commonService.send(sender, MessageKey.REGISTER_SUCCESS);
logger.info(sender.getName() + " registered " + playerName);
final Player player = bukkitService.getPlayerExact(playerName);
if (player != null) {
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(() ->
player.kickPlayer(commonService.retrieveSingleMessage(player, MessageKey.KICK_FOR_ADMIN_REGISTER)));
}
});
}
}

View File

@ -0,0 +1,77 @@
package fr.xephi.authme.command.executable.authme;
import ch.jalu.injector.factory.SingletonStore;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.SettingsWarner;
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.List;
/**
* The reload command.
*/
public class ReloadCommand implements ExecutableCommand {
private final ConsoleLogger logger = ConsoleLoggerFactory.get(ReloadCommand.class);
@Inject
private AuthMe plugin;
@Inject
private Settings settings;
@Inject
private DataSource dataSource;
@Inject
private CommonService commonService;
@Inject
private SettingsWarner settingsWarner;
@Inject
private SingletonStore<Reloadable> reloadableStore;
@Inject
private SingletonStore<SettingsDependent> settingsDependentStore;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
try {
settings.reload();
ConsoleLoggerFactory.reloadSettings(settings);
settingsWarner.logWarningsForMisconfigurations();
// 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())) {
Utils.logAndSendMessage(sender, "Note: cannot change database type during /authme reload");
}
performReloadOnServices();
commonService.send(sender, MessageKey.CONFIG_RELOAD_SUCCESS);
} catch (Exception e) {
sender.sendMessage("Error occurred during reload of AuthMe: aborting");
logger.logException("Aborting! Encountered exception during reload of AuthMe:", e);
plugin.stopOrUnload();
}
}
private void performReloadOnServices() {
reloadableStore.retrieveAllOfType()
.forEach(r -> r.reload());
settingsDependentStore.retrieveAllOfType()
.forEach(s -> s.reload(settings));
}
}

View File

@ -0,0 +1,78 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.ValidationService;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
/**
* Admin command for setting an email to an account.
*/
public class SetEmailCommand implements ExecutableCommand {
@Inject
private DataSource dataSource;
@Inject
private CommonService commonService;
@Inject
private PlayerCache playerCache;
@Inject
private BukkitService bukkitService;
@Inject
private ValidationService validationService;
@Override
public void executeCommand(final CommandSender sender, List<String> arguments) {
// Get the player name and email address
final String playerName = arguments.get(0);
final String playerEmail = arguments.get(1);
// Validate the email address
if (!validationService.validateEmail(playerEmail)) {
commonService.send(sender, MessageKey.INVALID_EMAIL);
return;
}
bukkitService.runTaskOptionallyAsync(new Runnable() {
@Override
public void run() {
// Validate the user
PlayerAuth auth = dataSource.getAuth(playerName);
if (auth == null) {
commonService.send(sender, MessageKey.UNKNOWN_USER);
return;
} else if (!validationService.isEmailFreeForRegistration(playerEmail, sender)) {
commonService.send(sender, MessageKey.EMAIL_ALREADY_USED_ERROR);
return;
}
// Set the email address
auth.setEmail(playerEmail);
if (!dataSource.updateEmail(auth)) {
commonService.send(sender, MessageKey.ERROR);
return;
}
// Update the player cache
if (playerCache.getAuth(playerName) != null) {
playerCache.updatePlayer(auth);
}
// Show a status message
commonService.send(sender, MessageKey.EMAIL_CHANGED_SUCCESS);
}
});
}
}

View File

@ -0,0 +1,23 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.settings.SpawnLoader;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
public class SetFirstSpawnCommand extends PlayerCommand {
@Inject
private SpawnLoader spawnLoader;
@Override
public void runCommand(Player player, List<String> arguments) {
if (spawnLoader.setFirstSpawn(player.getLocation())) {
player.sendMessage("[AuthMe] Correctly defined new first spawn point");
} else {
player.sendMessage("[AuthMe] SetFirstSpawn has failed, please retry");
}
}
}

View File

@ -0,0 +1,23 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.settings.SpawnLoader;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
public class SetSpawnCommand extends PlayerCommand {
@Inject
private SpawnLoader spawnLoader;
@Override
public void runCommand(Player player, List<String> arguments) {
if (spawnLoader.setSpawn(player.getLocation())) {
player.sendMessage("[AuthMe] Correctly defined new spawn point");
} else {
player.sendMessage("[AuthMe] SetSpawn has failed, please retry");
}
}
}

View File

@ -0,0 +1,23 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.settings.SpawnLoader;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
public class SpawnCommand extends PlayerCommand {
@Inject
private SpawnLoader spawnLoader;
@Override
public void runCommand(Player player, List<String> arguments) {
if (spawnLoader.getSpawn() == null) {
player.sendMessage("[AuthMe] Spawn has failed, please try to define the spawn");
} else {
player.teleport(spawnLoader.getSpawn());
}
}
}

View File

@ -0,0 +1,52 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.CommandMapper;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.command.FoundCommandResult;
import fr.xephi.authme.command.help.HelpProvider;
import fr.xephi.authme.service.AntiBotService;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.List;
/**
* Display or change the status of the antibot mod.
*/
public class SwitchAntiBotCommand implements ExecutableCommand {
@Inject
private AntiBotService antiBotService;
@Inject
private CommandMapper commandMapper;
@Inject
private HelpProvider helpProvider;
@Override
public void executeCommand(final CommandSender sender, List<String> arguments) {
if (arguments.isEmpty()) {
sender.sendMessage("[AuthMe] AntiBot status: " + antiBotService.getAntiBotStatus().name());
return;
}
String newState = arguments.get(0);
// Enable or disable the mod
if ("ON".equalsIgnoreCase(newState)) {
antiBotService.overrideAntiBotStatus(true);
sender.sendMessage("[AuthMe] AntiBot Manual Override: enabled!");
} else if ("OFF".equalsIgnoreCase(newState)) {
antiBotService.overrideAntiBotStatus(false);
sender.sendMessage("[AuthMe] AntiBot Manual Override: disabled!");
} else {
sender.sendMessage(ChatColor.DARK_RED + "Invalid AntiBot mode!");
FoundCommandResult result = commandMapper.mapPartsToCommand(sender, Arrays.asList("authme", "antibot"));
helpProvider.outputHelp(sender, result, HelpProvider.SHOW_ARGUMENTS);
sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE + "/authme help antibot");
}
}
}

View File

@ -0,0 +1,61 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.service.BukkitService;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Command to disable two-factor authentication for a user.
*/
public class TotpDisableAdminCommand implements ExecutableCommand {
private final ConsoleLogger logger = ConsoleLoggerFactory.get(TotpDisableAdminCommand.class);
@Inject
private DataSource dataSource;
@Inject
private Messages messages;
@Inject
private BukkitService bukkitService;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
String player = arguments.get(0);
PlayerAuth auth = dataSource.getAuth(player);
if (auth == null) {
messages.send(sender, MessageKey.UNKNOWN_USER);
} else if (auth.getTotpKey() == null) {
sender.sendMessage(ChatColor.RED + "Player '" + player + "' does not have two-factor auth enabled");
} else {
removeTotpKey(sender, player);
}
}
private void removeTotpKey(CommandSender sender, String player) {
if (dataSource.removeTotpKey(player)) {
sender.sendMessage("Disabled two-factor authentication successfully for '" + player + "'");
logger.info(sender.getName() + " disable two-factor authentication for '" + player + "'");
Player onlinePlayer = bukkitService.getPlayerExact(player);
if (onlinePlayer != null) {
messages.send(onlinePlayer, MessageKey.TWO_FACTOR_REMOVED_SUCCESS);
}
} else {
messages.send(sender, MessageKey.ERROR);
}
}
}

View File

@ -0,0 +1,38 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
/**
* Command to see whether a user has enabled two-factor authentication.
*/
public class TotpViewStatusCommand implements ExecutableCommand {
@Inject
private DataSource dataSource;
@Inject
private Messages messages;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
String player = arguments.get(0);
PlayerAuth auth = dataSource.getAuth(player);
if (auth == null) {
messages.send(sender, MessageKey.UNKNOWN_USER);
} else if (auth.getTotpKey() == null) {
sender.sendMessage(ChatColor.RED + "Player '" + player + "' does NOT have two-factor auth enabled");
} else {
sender.sendMessage(ChatColor.DARK_GREEN + "Player '" + player + "' has enabled two-factor authentication");
}
}
}

View File

@ -0,0 +1,49 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Admin command to unregister a player.
*/
public class UnregisterAdminCommand implements ExecutableCommand {
@Inject
private DataSource dataSource;
@Inject
private CommonService commonService;
@Inject
private BukkitService bukkitService;
@Inject
private Management management;
UnregisterAdminCommand() {
}
@Override
public void executeCommand(final CommandSender sender, List<String> arguments) {
String playerName = arguments.get(0);
// Make sure the user exists
if (!dataSource.isAuthAvailable(playerName)) {
commonService.send(sender, MessageKey.UNKNOWN_USER);
return;
}
// Get the player from the server and perform unregister
Player target = bukkitService.getPlayerExact(playerName);
management.performUnregisterByAdmin(sender, playerName, target);
}
}

View File

@ -0,0 +1,39 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.command.help.HelpMessagesService;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.service.HelpTranslationGenerator;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.util.List;
/**
* Messages command, updates the user's help messages file with any missing files
* from the provided file in the JAR.
*/
public class UpdateHelpMessagesCommand implements ExecutableCommand {
private final ConsoleLogger logger = ConsoleLoggerFactory.get(UpdateHelpMessagesCommand.class);
@Inject
private HelpTranslationGenerator helpTranslationGenerator;
@Inject
private HelpMessagesService helpMessagesService;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
try {
File updatedFile = helpTranslationGenerator.updateHelpFile();
sender.sendMessage("Successfully updated the help file '" + updatedFile.getName() + "'");
helpMessagesService.reloadMessagesFile();
} catch (IOException e) {
sender.sendMessage("Could not update help file: " + e.getMessage());
logger.logException("Could not update help file:", e);
}
}
}

View File

@ -0,0 +1,94 @@
package fr.xephi.authme.command.executable.authme;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.service.BukkitService;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;
public class VersionCommand implements ExecutableCommand {
@Inject
private BukkitService bukkitService;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
// Show some version info
sender.sendMessage(ChatColor.GOLD + "==========[ " + AuthMe.getPluginName() + " ABOUT ]==========");
sender.sendMessage(ChatColor.GOLD + "Version: " + ChatColor.WHITE + AuthMe.getPluginName()
+ " v" + AuthMe.getPluginVersion() + ChatColor.GRAY + " (build: " + AuthMe.getPluginBuildNumber() + ")");
sender.sendMessage(ChatColor.GOLD + "Authors:");
Collection<Player> onlinePlayers = bukkitService.getOnlinePlayers();
printDeveloper(sender, "Gabriele C.", "sgdc3", "Project manager, Contributor", onlinePlayers);
printDeveloper(sender, "Lucas J.", "ljacqu", "Main Developer", onlinePlayers);
printDeveloper(sender, "games647", "games647", "Developer", onlinePlayers);
printDeveloper(sender, "Hex3l", "Hex3l", "Developer", onlinePlayers);
printDeveloper(sender, "krusic22", "krusic22", "Support", onlinePlayers);
sender.sendMessage(ChatColor.GOLD + "Retired authors:");
printDeveloper(sender, "Alexandre Vanhecke", "xephi59", "Original Author", onlinePlayers);
printDeveloper(sender, "Gnat008", "gnat008", "Developer, Retired", onlinePlayers);
printDeveloper(sender, "DNx5", "DNx5", "Developer, Retired", onlinePlayers);
printDeveloper(sender, "Tim Visee", "timvisee", "Developer, Retired", onlinePlayers);
sender.sendMessage(ChatColor.GOLD + "Website: " + ChatColor.WHITE
+ "https://github.com/AuthMe/AuthMeReloaded");
sender.sendMessage(ChatColor.GOLD + "License: " + ChatColor.WHITE + "GNU GPL v3.0"
+ ChatColor.GRAY + ChatColor.ITALIC + " (See LICENSE file)");
sender.sendMessage(ChatColor.GOLD + "Copyright: " + ChatColor.WHITE
+ "Copyright (c) AuthMe-Team " + new SimpleDateFormat("yyyy").format(new Date())
+ ". Released under GPL v3 License.");
}
/**
* Print a developer with proper styling.
*
* @param sender The command sender
* @param name The display name of the developer
* @param minecraftName The Minecraft username of the developer, if available
* @param function The function of the developer
* @param onlinePlayers The list of online players
*/
private static void printDeveloper(CommandSender sender, String name, String minecraftName, String function,
Collection<Player> onlinePlayers) {
// Print the name
StringBuilder msg = new StringBuilder();
msg.append(" ")
.append(ChatColor.WHITE)
.append(name);
// Append the Minecraft name
msg.append(ChatColor.GRAY).append(" // ").append(ChatColor.WHITE).append(minecraftName);
msg.append(ChatColor.GRAY).append(ChatColor.ITALIC).append(" (").append(function).append(")");
// Show the online status
if (isPlayerOnline(minecraftName, onlinePlayers)) {
msg.append(ChatColor.GREEN).append(ChatColor.ITALIC).append(" (In-Game)");
}
// Print the message
sender.sendMessage(msg.toString());
}
/**
* Check whether a player is online.
*
* @param minecraftName The Minecraft player name
* @param onlinePlayers List of online players
*
* @return True if the player is online, false otherwise
*/
private static boolean isPlayerOnline(String minecraftName, Collection<Player> onlinePlayers) {
for (Player player : onlinePlayers) {
if (player.getName().equalsIgnoreCase(minecraftName)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,88 @@
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.permission.DebugSectionPermissions;
import fr.xephi.authme.permission.PermissionNode;
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) {
sender.sendMessage(ChatColor.BLUE + "AuthMe country lookup");
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);
}
}
@Override
public PermissionNode getRequiredPermission() {
return DebugSectionPermissions.COUNTRY_LOOKUP;
}
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");
}
// TODO #1366: Extend with registration IP?
private void outputInfoForPlayer(CommandSender sender, String name) {
PlayerAuth auth = dataSource.getAuth(name);
if (auth == null) {
sender.sendMessage("No player with name '" + name + "'");
} else if (auth.getLastIp() == null) {
sender.sendMessage("No last IP address known for '" + name + "'");
} else {
sender.sendMessage("Player '" + name + "' has IP address " + auth.getLastIp());
outputInfoForIpAddr(sender, auth.getLastIp());
}
}
}

View File

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

View File

@ -0,0 +1,85 @@
package fr.xephi.authme.command.executable.authme.debug;
import ch.jalu.injector.factory.Factory;
import com.google.common.collect.ImmutableSet;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.permission.PermissionsManager;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* Debug command main.
*/
public class DebugCommand implements ExecutableCommand {
private static final Set<Class<? extends DebugSection>> SECTION_CLASSES = ImmutableSet.of(
PermissionGroups.class, DataStatistics.class, CountryLookup.class, PlayerAuthViewer.class, InputValidator.class,
LimboPlayerViewer.class, CountryLookup.class, HasPermissionChecker.class, TestEmailSender.class,
SpawnLocationViewer.class, MySqlDefaultChanger.class);
@Inject
private Factory<DebugSection> debugSectionFactory;
@Inject
private PermissionsManager permissionsManager;
private Map<String, DebugSection> sections;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
DebugSection debugSection = findDebugSection(arguments);
if (debugSection == null) {
sendAvailableSections(sender);
} else {
executeSection(debugSection, sender, arguments);
}
}
private DebugSection findDebugSection(List<String> arguments) {
if (arguments.isEmpty()) {
return null;
}
return getSections().get(arguments.get(0).toLowerCase(Locale.ROOT));
}
private void sendAvailableSections(CommandSender sender) {
sender.sendMessage(ChatColor.BLUE + "AuthMe debug utils");
sender.sendMessage("Sections available to you:");
long availableSections = getSections().values().stream()
.filter(section -> permissionsManager.hasPermission(sender, section.getRequiredPermission()))
.peek(e -> sender.sendMessage("- " + e.getName() + ": " + e.getDescription()))
.count();
if (availableSections == 0) {
sender.sendMessage(ChatColor.RED + "You don't have permission to view any debug section");
}
}
private void executeSection(DebugSection section, CommandSender sender, List<String> arguments) {
if (permissionsManager.hasPermission(sender, section.getRequiredPermission())) {
section.execute(sender, arguments.subList(1, arguments.size()));
} else {
sender.sendMessage(ChatColor.RED + "You don't have permission for this section. See /authme debug");
}
}
// Lazy getter
private Map<String, DebugSection> getSections() {
if (sections == null) {
Map<String, DebugSection> sections = new TreeMap<>();
for (Class<? extends DebugSection> sectionClass : SECTION_CLASSES) {
DebugSection section = debugSectionFactory.newInstance(sectionClass);
sections.put(section.getName(), section);
}
this.sections = sections;
}
return sections;
}
}

View File

@ -0,0 +1,36 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.permission.PermissionNode;
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);
/**
* @return permission required to run this section
*/
PermissionNode getRequiredPermission();
}

View File

@ -0,0 +1,130 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.datasource.CacheDataSource;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import org.bukkit.Location;
import java.lang.reflect.Field;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
/**
* Utilities used within the DebugSection implementations.
*/
final class DebugSectionUtils {
private static ConsoleLogger logger = ConsoleLoggerFactory.get(DebugSectionUtils.class);
private static Field limboEntriesField;
private DebugSectionUtils() {
}
/**
* Formats the given location in a human readable way. Null-safe.
*
* @param location the location to format
* @return the formatted location
*/
static String formatLocation(Location location) {
if (location == null) {
return "null";
}
String worldName = location.getWorld() == null ? "null" : location.getWorld().getName();
return formatLocation(location.getX(), location.getY(), location.getZ(), worldName);
}
/**
* Formats the given location in a human readable way.
*
* @param x the x coordinate
* @param y the y coordinate
* @param z the z coordinate
* @param world the world name
* @return the formatted location
*/
static String formatLocation(double x, double y, double z, String world) {
return "(" + round(x) + ", " + round(y) + ", " + round(z) + ") in '" + world + "'";
}
/**
* Rounds the given number to two decimals.
*
* @param number the number to round
* @return the rounded number
*/
private static String round(double number) {
DecimalFormat df = new DecimalFormat("#.##");
df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.US));
df.setRoundingMode(RoundingMode.HALF_UP);
return df.format(number);
}
private static Field getLimboPlayerEntriesField() {
if (limboEntriesField == null) {
try {
Field field = LimboService.class.getDeclaredField("entries");
field.setAccessible(true);
limboEntriesField = field;
} catch (Exception e) {
logger.logException("Could not retrieve LimboService entries field:", e);
}
}
return limboEntriesField;
}
/**
* Applies the given function to the map in LimboService containing the LimboPlayers.
* As we don't want to expose this information in non-debug settings, this is done with reflection.
* Exceptions are generously caught and {@code null} is returned on failure.
*
* @param limboService the limbo service instance to get the map from
* @param function the function to apply to the map
* @param <U> the result type of the function
*
* @return the value of the function applied to the map, or null upon error
*/
static <U> U applyToLimboPlayersMap(LimboService limboService, Function<Map, U> function) {
Field limboPlayerEntriesField = getLimboPlayerEntriesField();
if (limboPlayerEntriesField != null) {
try {
return function.apply((Map) limboEntriesField.get(limboService));
} catch (Exception e) {
logger.logException("Could not retrieve LimboService values:", e);
}
}
return null;
}
static <T> T castToTypeOrNull(Object object, Class<T> clazz) {
return clazz.isInstance(object) ? clazz.cast(object) : null;
}
/**
* Unwraps the "cache data source" and returns the underlying source. Returns the
* same as the input argument otherwise.
*
* @param dataSource the data source to unwrap if applicable
* @return the non-cache data source
*/
static DataSource unwrapSourceFromCacheDataSource(DataSource dataSource) {
if (dataSource instanceof CacheDataSource) {
try {
Field source = CacheDataSource.class.getDeclaredField("source");
source.setAccessible(true);
return (DataSource) source.get(dataSource);
} catch (NoSuchFieldException | IllegalAccessException e) {
logger.logException("Could not get source of CacheDataSource:", e);
return null;
}
}
return dataSource;
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,327 @@
package fr.xephi.authme.command.executable.authme.debug;
import ch.jalu.configme.properties.Property;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.MySQL;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.permission.DebugSectionPermissions;
import fr.xephi.authme.permission.PermissionNode;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.castToTypeOrNull;
import static fr.xephi.authme.command.executable.authme.debug.DebugSectionUtils.unwrapSourceFromCacheDataSource;
import static fr.xephi.authme.data.auth.PlayerAuth.DB_EMAIL_DEFAULT;
import static fr.xephi.authme.data.auth.PlayerAuth.DB_LAST_IP_DEFAULT;
import static fr.xephi.authme.data.auth.PlayerAuth.DB_LAST_LOGIN_DEFAULT;
import static fr.xephi.authme.datasource.SqlDataSourceUtils.getColumnDefaultValue;
import static fr.xephi.authme.datasource.SqlDataSourceUtils.isNotNullColumn;
import static java.lang.String.format;
/**
* Convenience command to add or remove the default value of a column and its nullable status
* in the MySQL data source.
*/
class MySqlDefaultChanger implements DebugSection {
private static final String NOT_NULL_SUFFIX = ChatColor.DARK_AQUA + "@" + ChatColor.RESET;
private static final String DEFAULT_VALUE_SUFFIX = ChatColor.GOLD + "#" + ChatColor.RESET;
private ConsoleLogger logger = ConsoleLoggerFactory.get(MySqlDefaultChanger.class);
@Inject
private Settings settings;
@Inject
private DataSource dataSource;
private MySQL mySql;
@PostConstruct
void setMySqlField() {
this.mySql = castToTypeOrNull(unwrapSourceFromCacheDataSource(this.dataSource), MySQL.class);
}
@Override
public String getName() {
return "mysqldef";
}
@Override
public String getDescription() {
return "Add or remove the default value of MySQL columns";
}
@Override
public PermissionNode getRequiredPermission() {
return DebugSectionPermissions.MYSQL_DEFAULT_CHANGER;
}
@Override
public void execute(CommandSender sender, List<String> arguments) {
if (mySql == null) {
sender.sendMessage("Defaults can be changed for the MySQL data source only.");
return;
}
Operation operation = matchToEnum(arguments, 0, Operation.class);
Columns column = matchToEnum(arguments, 1, Columns.class);
if (operation == Operation.DETAILS) {
showColumnDetails(sender);
} else if (operation == null || column == null) {
displayUsageHints(sender);
} else {
sender.sendMessage(ChatColor.BLUE + "[AuthMe] MySQL change '" + column + "'");
try (Connection con = getConnection(mySql)) {
switch (operation) {
case ADD:
changeColumnToNotNullWithDefault(sender, column, con);
break;
case REMOVE:
removeNotNullAndDefault(sender, column, con);
break;
default:
throw new IllegalStateException("Unknown operation '" + operation + "'");
}
} catch (SQLException | IllegalStateException e) {
logger.logException("Failed to perform MySQL default altering operation:", e);
}
}
}
/**
* Adds a default value to the column definition and adds a {@code NOT NULL} constraint for
* the specified column.
*
* @param sender the command sender initiation the action
* @param column the column to modify
* @param con connection to the database
* @throws SQLException .
*/
private void changeColumnToNotNullWithDefault(CommandSender sender, Columns column,
Connection con) throws SQLException {
final String tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
final String columnName = settings.getProperty(column.getColumnNameProperty());
// Replace NULLs with future default value
String sql = format("UPDATE %s SET %s = ? WHERE %s IS NULL;", tableName, columnName, columnName);
int updatedRows;
try (PreparedStatement pst = con.prepareStatement(sql)) {
pst.setObject(1, column.getDefaultValue());
updatedRows = pst.executeUpdate();
}
sender.sendMessage("Replaced NULLs with default value ('" + column.getDefaultValue()
+ "'), modifying " + updatedRows + " entries");
// Change column definition to NOT NULL version
try (Statement st = con.createStatement()) {
st.execute(format("ALTER TABLE %s MODIFY %s %s", tableName, columnName, column.getNotNullDefinition()));
sender.sendMessage("Changed column '" + columnName + "' to have NOT NULL constraint");
}
// Log success message
logger.info("Changed MySQL column '" + columnName + "' to be NOT NULL, as initiated by '"
+ sender.getName() + "'");
}
/**
* Removes the {@code NOT NULL} constraint of a column definition and replaces rows with the
* default value to {@code NULL}.
*
* @param sender the command sender initiation the action
* @param column the column to modify
* @param con connection to the database
* @throws SQLException .
*/
private void removeNotNullAndDefault(CommandSender sender, Columns column, Connection con) throws SQLException {
final String tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
final String columnName = settings.getProperty(column.getColumnNameProperty());
// Change column definition to nullable version
try (Statement st = con.createStatement()) {
st.execute(format("ALTER TABLE %s MODIFY %s %s", tableName, columnName, column.getNullableDefinition()));
sender.sendMessage("Changed column '" + columnName + "' to allow nulls");
}
// Replace old default value with NULL
String sql = format("UPDATE %s SET %s = NULL WHERE %s = ?;", tableName, columnName, columnName);
int updatedRows;
try (PreparedStatement pst = con.prepareStatement(sql)) {
pst.setObject(1, column.getDefaultValue());
updatedRows = pst.executeUpdate();
}
sender.sendMessage("Replaced default value ('" + column.getDefaultValue()
+ "') to be NULL, modifying " + updatedRows + " entries");
// Log success message
logger.info("Changed MySQL column '" + columnName + "' to allow NULL, as initiated by '"
+ sender.getName() + "'");
}
/**
* Outputs the current definitions of all {@link Columns} which can be migrated.
*
* @param sender command sender to output the data to
*/
private void showColumnDetails(CommandSender sender) {
sender.sendMessage(ChatColor.BLUE + "MySQL column details");
final String tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
try (Connection con = getConnection(mySql)) {
final DatabaseMetaData metaData = con.getMetaData();
for (Columns col : Columns.values()) {
String columnName = settings.getProperty(col.getColumnNameProperty());
String isNullText = isNotNullColumn(metaData, tableName, columnName) ? "NOT NULL" : "nullable";
Object defaultValue = getColumnDefaultValue(metaData, tableName, columnName);
String defaultText = defaultValue == null ? "no default" : "default: '" + defaultValue + "'";
sender.sendMessage(formatColumnWithMetadata(col, metaData, tableName)
+ " (" + columnName + "): " + isNullText + ", " + defaultText);
}
} catch (SQLException e) {
logger.logException("Failed while showing column details:", e);
sender.sendMessage("Failed while showing column details. See log for info");
}
}
/**
* Displays sample commands and the list of columns that can be changed.
*
* @param sender the sender issuing the command
*/
private void displayUsageHints(CommandSender sender) {
sender.sendMessage(ChatColor.BLUE + "MySQL column changer");
sender.sendMessage("Adds or removes a NOT NULL constraint for a column.");
sender.sendMessage("Examples: add a NOT NULL constraint with");
sender.sendMessage(" /authme debug mysqldef add <column>");
sender.sendMessage("Remove one with /authme debug mysqldef remove <column>");
sender.sendMessage("Available columns: " + constructColumnListWithMetadata());
sender.sendMessage(" " + NOT_NULL_SUFFIX + ": not-null, " + DEFAULT_VALUE_SUFFIX
+ ": has default. See /authme debug mysqldef details");
}
/**
* @return list of {@link Columns} we can toggle with suffixes indicating their NOT NULL and default value status
*/
private String constructColumnListWithMetadata() {
try (Connection con = getConnection(mySql)) {
final DatabaseMetaData metaData = con.getMetaData();
final String tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
List<String> formattedColumns = new ArrayList<>(Columns.values().length);
for (Columns col : Columns.values()) {
formattedColumns.add(formatColumnWithMetadata(col, metaData, tableName));
}
return String.join(ChatColor.RESET + ", ", formattedColumns);
} catch (SQLException e) {
logger.logException("Failed to construct column list:", e);
return ChatColor.RED + "An error occurred! Please see the console for details.";
}
}
private String formatColumnWithMetadata(Columns column, DatabaseMetaData metaData,
String tableName) throws SQLException {
String columnName = settings.getProperty(column.getColumnNameProperty());
boolean isNotNull = isNotNullColumn(metaData, tableName, columnName);
boolean hasDefaultValue = getColumnDefaultValue(metaData, tableName, columnName) != null;
return column.name()
+ (isNotNull ? NOT_NULL_SUFFIX : "")
+ (hasDefaultValue ? DEFAULT_VALUE_SUFFIX : "");
}
/**
* Gets the Connection object from the MySQL data source.
*
* @param mySql the MySQL data source to get the connection from
* @return the connection
*/
@VisibleForTesting
Connection getConnection(MySQL mySql) {
try {
Method method = MySQL.class.getDeclaredMethod("getConnection");
method.setAccessible(true);
return (Connection) method.invoke(mySql);
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw new IllegalStateException("Could not get MySQL connection", e);
}
}
private static <E extends Enum<E>> E matchToEnum(List<String> arguments, int index, Class<E> clazz) {
if (arguments.size() <= index) {
return null;
}
String str = arguments.get(index);
return Arrays.stream(clazz.getEnumConstants())
.filter(e -> e.name().equalsIgnoreCase(str))
.findFirst().orElse(null);
}
private enum Operation {
ADD, REMOVE, DETAILS
}
/** MySQL columns which can be toggled between being NOT NULL and allowing NULL values. */
enum Columns {
LASTLOGIN(DatabaseSettings.MYSQL_COL_LASTLOGIN,
"BIGINT", "BIGINT NOT NULL DEFAULT 0", DB_LAST_LOGIN_DEFAULT),
LASTIP(DatabaseSettings.MYSQL_COL_LAST_IP,
"VARCHAR(40) CHARACTER SET ascii COLLATE ascii_bin",
"VARCHAR(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '127.0.0.1'",
DB_LAST_IP_DEFAULT),
EMAIL(DatabaseSettings.MYSQL_COL_EMAIL,
"VARCHAR(255)", "VARCHAR(255) NOT NULL DEFAULT 'your@email.com'", DB_EMAIL_DEFAULT);
private final Property<String> columnNameProperty;
private final String nullableDefinition;
private final String notNullDefinition;
private final Object defaultValue;
Columns(Property<String> columnNameProperty, String nullableDefinition,
String notNullDefinition, Object defaultValue) {
this.columnNameProperty = columnNameProperty;
this.nullableDefinition = nullableDefinition;
this.notNullDefinition = notNullDefinition;
this.defaultValue = defaultValue;
}
/** @return property defining the column name in the database */
Property<String> getColumnNameProperty() {
return columnNameProperty;
}
/** @return SQL definition of the column allowing NULL values */
String getNullableDefinition() {
return nullableDefinition;
}
/** @return SQL definition of the column with a NOT NULL constraint */
String getNotNullDefinition() {
return notNullDefinition;
}
/** @return the default value used in {@link #notNullDefinition} */
Object getDefaultValue() {
return defaultValue;
}
}
}

View File

@ -0,0 +1,56 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.data.limbo.UserGroup;
import fr.xephi.authme.permission.DebugSectionPermissions;
import fr.xephi.authme.permission.PermissionNode;
import fr.xephi.authme.permission.PermissionsManager;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
import static java.util.stream.Collectors.toList;
/**
* 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) {
sender.sendMessage(ChatColor.BLUE + "AuthMe permission groups");
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 {
List<String> groupNames = permissionsManager.getGroups(player).stream()
.map(UserGroup::getGroupName)
.collect(toList());
sender.sendMessage("Player " + name + " has permission groups: " + String.join(", ", groupNames));
sender.sendMessage("Primary group is: " + permissionsManager.getGroups(player));
}
}
@Override
public PermissionNode getRequiredPermission() {
return DebugSectionPermissions.PERM_GROUPS;
}
}

View File

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

View File

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

View File

@ -0,0 +1,125 @@
package fr.xephi.authme.command.executable.authme.debug;
import ch.jalu.datasourcecolumns.data.DataSourceValue;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.mail.SendMailSsl;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.permission.DebugSectionPermissions;
import fr.xephi.authme.permission.PermissionNode;
import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Utils;
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 {
private final ConsoleLogger logger = ConsoleLoggerFactory.get(TestEmailSender.class);
@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) {
sender.sendMessage(ChatColor.BLUE + "AuthMe test email sender");
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");
}
}
}
@Override
public PermissionNode getRequiredPermission() {
return DebugSectionPermissions.TEST_EMAIL;
}
/**
* Gets the email address to use based on the sender and the arguments. If the arguments are empty,
* we attempt to retrieve the email from the sender. If there is an argument, we verify that it is
* an email address.
* {@code null} is returned if no email address could be found. This method informs the sender of
* the specific error in such cases.
*
* @param sender the command sender
* @param arguments the provided arguments
* @return the email to use, or null if none found
*/
private String getEmail(CommandSender sender, List<String> arguments) {
if (arguments.isEmpty()) {
DataSourceValue<String> emailResult = dataSource.getEmail(sender.getName());
if (!emailResult.rowExists()) {
sender.sendMessage(ChatColor.RED + "Please provide an email address, "
+ "e.g. /authme debug mail test@example.com");
return null;
}
final String email = emailResult.getValue();
if (Utils.isEmailEmpty(email)) {
sender.sendMessage(ChatColor.RED + "No email set for your account!"
+ " Please use /authme debug mail <email>");
return null;
}
return email;
} else {
String email = arguments.get(0);
if (StringUtils.isInsideString('@', email)) {
return email;
}
sender.sendMessage(ChatColor.RED + "Invalid email! Usage: /authme debug mail test@example.com");
return null;
}
}
private boolean sendTestEmail(String email) {
HtmlEmail htmlEmail;
try {
htmlEmail = sendMailSsl.initializeMail(email);
} catch (EmailException e) {
logger.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

@ -0,0 +1,86 @@
package fr.xephi.authme.command.executable.captcha;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.data.captcha.LoginCaptchaManager;
import fr.xephi.authme.data.captcha.RegistrationCaptchaManager;
import fr.xephi.authme.data.limbo.LimboMessageType;
import fr.xephi.authme.data.limbo.LimboService;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Captcha command, allowing a player to solve a captcha.
*/
public class CaptchaCommand extends PlayerCommand {
@Inject
private PlayerCache playerCache;
@Inject
private LoginCaptchaManager loginCaptchaManager;
@Inject
private RegistrationCaptchaManager registrationCaptchaManager;
@Inject
private CommonService commonService;
@Inject
private LimboService limboService;
@Inject
private DataSource dataSource;
@Override
public void runCommand(Player player, List<String> arguments) {
final String name = player.getName();
if (playerCache.isAuthenticated(name)) {
// No captcha is relevant if the player is logged in
commonService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR);
return;
}
if (loginCaptchaManager.isCaptchaRequired(name)) {
checkLoginCaptcha(player, arguments.get(0));
} else {
final boolean isPlayerRegistered = dataSource.isAuthAvailable(name);
if (!isPlayerRegistered && registrationCaptchaManager.isCaptchaRequired(name)) {
checkRegisterCaptcha(player, arguments.get(0));
} else {
MessageKey errorMessage = isPlayerRegistered ? MessageKey.USAGE_LOGIN : MessageKey.USAGE_REGISTER;
commonService.send(player, errorMessage);
}
}
}
private void checkLoginCaptcha(Player player, String captchaCode) {
final boolean isCorrectCode = loginCaptchaManager.checkCode(player, captchaCode);
if (isCorrectCode) {
commonService.send(player, MessageKey.CAPTCHA_SUCCESS);
commonService.send(player, MessageKey.LOGIN_MESSAGE);
limboService.unmuteMessageTask(player);
} else {
String newCode = loginCaptchaManager.getCaptchaCodeOrGenerateNew(player.getName());
commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode);
}
}
private void checkRegisterCaptcha(Player player, String captchaCode) {
final boolean isCorrectCode = registrationCaptchaManager.checkCode(player, captchaCode);
if (isCorrectCode) {
commonService.send(player, MessageKey.REGISTER_CAPTCHA_SUCCESS);
commonService.send(player, MessageKey.REGISTER_MESSAGE);
} else {
String newCode = registrationCaptchaManager.getCaptchaCodeOrGenerateNew(player.getName());
commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode);
}
limboService.resetMessageTask(player, LimboMessageType.REGISTER);
}
}

View File

@ -0,0 +1,74 @@
package fr.xephi.authme.command.executable.changepassword;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.VerificationCodeManager;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.service.ValidationService.ValidationResult;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
import java.util.Locale;
/**
* The command for a player to change his password with.
*/
public class ChangePasswordCommand extends PlayerCommand {
@Inject
private CommonService commonService;
@Inject
private PlayerCache playerCache;
@Inject
private ValidationService validationService;
@Inject
private Management management;
@Inject
private VerificationCodeManager codeManager;
@Override
public void runCommand(Player player, List<String> arguments) {
String name = player.getName().toLowerCase(Locale.ROOT);
if (!playerCache.isAuthenticated(name)) {
commonService.send(player, MessageKey.NOT_LOGGED_IN);
return;
}
// Check if the user has been verified or not
if (codeManager.isVerificationRequired(player)) {
codeManager.codeExistOrGenerateNew(name);
commonService.send(player, MessageKey.VERIFICATION_CODE_REQUIRED);
return;
}
String oldPassword = arguments.get(0);
String newPassword = arguments.get(1);
// Make sure the password is allowed
ValidationResult passwordValidation = validationService.validatePassword(newPassword, name);
if (passwordValidation.hasError()) {
commonService.send(player, passwordValidation.getMessageKey(), passwordValidation.getArgs());
return;
}
management.performPasswordChange(player, oldPassword, newPassword);
}
@Override
protected String getAlternativeCommand() {
return "/authme password <playername> <password>";
}
@Override
public MessageKey getArgumentsMismatchMessage() {
return MessageKey.USAGE_CHANGE_PASSWORD;
}
}

View File

@ -0,0 +1,40 @@
package fr.xephi.authme.command.executable.email;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.service.CommonService;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Command for setting an email to an account.
*/
public class AddEmailCommand extends PlayerCommand {
@Inject
private Management management;
@Inject
private CommonService commonService;
@Override
public void runCommand(Player player, List<String> arguments) {
String email = arguments.get(0);
String emailConfirmation = arguments.get(1);
if (email.equals(emailConfirmation)) {
// Closer inspection of the mail address handled by the async task
management.performAddEmail(player, email);
} else {
commonService.send(player, MessageKey.CONFIRM_EMAIL_MESSAGE);
}
}
@Override
public MessageKey getArgumentsMismatchMessage() {
return MessageKey.USAGE_ADD_EMAIL;
}
}

View File

@ -0,0 +1,46 @@
package fr.xephi.authme.command.executable.email;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.VerificationCodeManager;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.service.CommonService;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Change email command.
*/
public class ChangeEmailCommand extends PlayerCommand {
@Inject
private Management management;
@Inject
private CommonService commonService;
@Inject
private VerificationCodeManager codeManager;
@Override
public void runCommand(Player player, List<String> arguments) {
final String playerName = player.getName();
// Check if the user has been verified or not
if (codeManager.isVerificationRequired(player)) {
codeManager.codeExistOrGenerateNew(playerName);
commonService.send(player, MessageKey.VERIFICATION_CODE_REQUIRED);
return;
}
String playerMailOld = arguments.get(0);
String playerMailNew = arguments.get(1);
management.performChangeEmail(player, playerMailOld, playerMailNew);
}
@Override
public MessageKey getArgumentsMismatchMessage() {
return MessageKey.USAGE_CHANGE_EMAIL;
}
}

View File

@ -0,0 +1,29 @@
package fr.xephi.authme.command.executable.email;
import fr.xephi.authme.command.CommandMapper;
import fr.xephi.authme.command.ExecutableCommand;
import fr.xephi.authme.command.FoundCommandResult;
import fr.xephi.authme.command.help.HelpProvider;
import org.bukkit.command.CommandSender;
import javax.inject.Inject;
import java.util.Collections;
import java.util.List;
/**
* Base command for /email, showing information about the child commands.
*/
public class EmailBaseCommand implements ExecutableCommand {
@Inject
private CommandMapper commandMapper;
@Inject
private HelpProvider helpProvider;
@Override
public void executeCommand(CommandSender sender, List<String> arguments) {
FoundCommandResult result = commandMapper.mapPartsToCommand(sender, Collections.singletonList("email"));
helpProvider.outputHelp(sender, result, HelpProvider.SHOW_CHILDREN);
}
}

View File

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

View File

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

View File

@ -0,0 +1,91 @@
package fr.xephi.authme.command.executable.email;
import ch.jalu.datasourcecolumns.data.DataSourceValue;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.PasswordRecoveryService;
import fr.xephi.authme.service.RecoveryCodeService;
import fr.xephi.authme.util.Utils;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Command for password recovery by email.
*/
public class RecoverEmailCommand extends PlayerCommand {
private final ConsoleLogger logger = ConsoleLoggerFactory.get(RecoverEmailCommand.class);
@Inject
private CommonService commonService;
@Inject
private DataSource dataSource;
@Inject
private PlayerCache playerCache;
@Inject
private EmailService emailService;
@Inject
private PasswordRecoveryService recoveryService;
@Inject
private RecoveryCodeService recoveryCodeService;
@Inject
private BukkitService bukkitService;
@Override
protected void runCommand(Player player, List<String> arguments) {
final String playerMail = arguments.get(0);
final String playerName = player.getName();
if (!emailService.hasAllInformation()) {
logger.warning("Mail API is not set");
commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS);
return;
}
if (playerCache.isAuthenticated(playerName)) {
commonService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR);
return;
}
DataSourceValue<String> emailResult = dataSource.getEmail(playerName);
if (!emailResult.rowExists()) {
commonService.send(player, MessageKey.USAGE_REGISTER);
return;
}
final String email = emailResult.getValue();
if (Utils.isEmailEmpty(email) || !email.equalsIgnoreCase(playerMail)) {
commonService.send(player, MessageKey.INVALID_EMAIL);
return;
}
bukkitService.runTaskAsynchronously(() -> {
if (recoveryCodeService.isRecoveryCodeNeeded()) {
// Recovery code is needed; generate and send one
recoveryService.createAndSendRecoveryCode(player, email);
} else {
// Code not needed, just send them a new password
recoveryService.generateAndSendNewPassword(player, email);
}
});
}
@Override
public MessageKey getArgumentsMismatchMessage() {
return MessageKey.USAGE_RECOVER_EMAIL;
}
}

View File

@ -0,0 +1,48 @@
package fr.xephi.authme.command.executable.email;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.Utils;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Show email command.
*/
public class ShowEmailCommand extends PlayerCommand {
@Inject
private CommonService commonService;
@Inject
private PlayerCache playerCache;
@Override
public void runCommand(Player player, List<String> arguments) {
PlayerAuth auth = playerCache.getAuth(player.getName());
if (auth != null && !Utils.isEmailEmpty(auth.getEmail())) {
if (commonService.getProperty(SecuritySettings.USE_EMAIL_MASKING)){
commonService.send(player, MessageKey.EMAIL_SHOW, emailMask(auth.getEmail()));
} else {
commonService.send(player, MessageKey.EMAIL_SHOW, auth.getEmail());
}
} else {
commonService.send(player, MessageKey.SHOW_NO_EMAIL);
}
}
private String emailMask(String email){
String[] frag = email.split("@"); //Split id and domain
int sid = frag[0].length() / 3 + 1; //Define the id view (required length >= 1)
int sdomain = frag[1].length() / 3; //Define the domain view (required length >= 0)
String id = frag[0].substring(0, sid) + "***"; //Build the id
String domain = "***" + frag[1].substring(sdomain); //Build the domain
return id + "@" + domain;
}
}

View File

@ -0,0 +1,34 @@
package fr.xephi.authme.command.executable.login;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.process.Management;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Login command.
*/
public class LoginCommand extends PlayerCommand {
@Inject
private Management management;
@Override
public void runCommand(Player player, List<String> arguments) {
String password = arguments.get(0);
management.performLogin(player, password);
}
@Override
public MessageKey getArgumentsMismatchMessage() {
return MessageKey.USAGE_LOGIN;
}
@Override
protected String getAlternativeCommand() {
return "/authme forcelogin <player>";
}
}

View File

@ -0,0 +1,22 @@
package fr.xephi.authme.command.executable.logout;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.process.Management;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Logout command.
*/
public class LogoutCommand extends PlayerCommand {
@Inject
private Management management;
@Override
public void runCommand(Player player, List<String> arguments) {
management.performLogout(player);
}
}

View File

@ -0,0 +1,225 @@
package fr.xephi.authme.command.executable.register;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.captcha.RegistrationCaptchaManager;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.mail.EmailService;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.process.Management;
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
import fr.xephi.authme.process.register.RegistrationType;
import fr.xephi.authme.process.register.executors.EmailRegisterParams;
import fr.xephi.authme.process.register.executors.PasswordRegisterParams;
import fr.xephi.authme.process.register.executors.RegistrationMethod;
import fr.xephi.authme.process.register.executors.TwoFactorRegisterParams;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.service.CommonService;
import fr.xephi.authme.service.ValidationService;
import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.RegistrationSettings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import static fr.xephi.authme.process.register.RegisterSecondaryArgument.CONFIRMATION;
import static fr.xephi.authme.process.register.RegisterSecondaryArgument.EMAIL_MANDATORY;
import static fr.xephi.authme.process.register.RegisterSecondaryArgument.EMAIL_OPTIONAL;
import static fr.xephi.authme.process.register.RegisterSecondaryArgument.NONE;
import static fr.xephi.authme.settings.properties.RegistrationSettings.REGISTER_SECOND_ARGUMENT;
/**
* Command for /register.
*/
public class RegisterCommand extends PlayerCommand {
private final ConsoleLogger logger = ConsoleLoggerFactory.get(RegisterCommand.class);
@Inject
private Management management;
@Inject
private CommonService commonService;
@Inject
private DataSource dataSource;
@Inject
private EmailService emailService;
@Inject
private ValidationService validationService;
@Inject
private RegistrationCaptchaManager registrationCaptchaManager;
@Override
public void runCommand(Player player, List<String> arguments) {
if (!isCaptchaFulfilled(player)) {
return; // isCaptchaFulfilled handles informing the player on failure
}
if (commonService.getProperty(SecuritySettings.PASSWORD_HASH) == HashAlgorithm.TWO_FACTOR) {
//for two factor auth we don't need to check the usage
management.performRegister(RegistrationMethod.TWO_FACTOR_REGISTRATION,
TwoFactorRegisterParams.of(player));
return;
} else if (arguments.size() < 1) {
commonService.send(player, MessageKey.USAGE_REGISTER);
return;
}
RegistrationType registrationType = commonService.getProperty(RegistrationSettings.REGISTRATION_TYPE);
if (registrationType == RegistrationType.PASSWORD) {
handlePasswordRegistration(player, arguments);
} else if (registrationType == RegistrationType.EMAIL) {
handleEmailRegistration(player, arguments);
} else {
throw new IllegalStateException("Unknown registration type '" + registrationType + "'");
}
}
@Override
protected String getAlternativeCommand() {
return "/authme register <playername> <password>";
}
@Override
public MessageKey getArgumentsMismatchMessage() {
return MessageKey.USAGE_REGISTER;
}
private boolean isCaptchaFulfilled(Player player) {
if (registrationCaptchaManager.isCaptchaRequired(player.getName())) {
String code = registrationCaptchaManager.getCaptchaCodeOrGenerateNew(player.getName());
commonService.send(player, MessageKey.CAPTCHA_FOR_REGISTRATION_REQUIRED, code);
return false;
}
return true;
}
private void handlePasswordRegistration(Player player, List<String> arguments) {
if (isSecondArgValidForPasswordRegistration(player, arguments)) {
final String password = arguments.get(0);
final String email = getEmailIfAvailable(arguments);
management.performRegister(RegistrationMethod.PASSWORD_REGISTRATION,
PasswordRegisterParams.of(player, password, email));
}
}
private String getEmailIfAvailable(List<String> arguments) {
if (arguments.size() >= 2) {
RegisterSecondaryArgument secondArgType = commonService.getProperty(REGISTER_SECOND_ARGUMENT);
if (secondArgType == EMAIL_MANDATORY || secondArgType == EMAIL_OPTIONAL) {
return arguments.get(1);
}
}
return null;
}
/**
* Verifies that the second argument is valid (based on the configuration)
* to perform a password registration. The player is informed if the check
* is unsuccessful.
*
* @param player the player to register
* @param arguments the provided arguments
* @return true if valid, false otherwise
*/
private boolean isSecondArgValidForPasswordRegistration(Player player, List<String> arguments) {
RegisterSecondaryArgument secondArgType = commonService.getProperty(REGISTER_SECOND_ARGUMENT);
// cases where args.size < 2
if (secondArgType == NONE || secondArgType == EMAIL_OPTIONAL && arguments.size() < 2) {
return true;
} else if (arguments.size() < 2) {
commonService.send(player, MessageKey.USAGE_REGISTER);
return false;
}
if (secondArgType == CONFIRMATION) {
if (arguments.get(0).equals(arguments.get(1))) {
return true;
} else {
commonService.send(player, MessageKey.PASSWORD_MATCH_ERROR);
return false;
}
} else if (secondArgType == EMAIL_MANDATORY || secondArgType == EMAIL_OPTIONAL) {
if (validationService.validateEmail(arguments.get(1))) {
return true;
} else {
commonService.send(player, MessageKey.INVALID_EMAIL);
return false;
}
} else {
throw new IllegalStateException("Unknown secondary argument type '" + secondArgType + "'");
}
}
private void handleEmailRegistration(Player player, List<String> arguments) {
if (!emailService.hasAllInformation()) {
commonService.send(player, MessageKey.INCOMPLETE_EMAIL_SETTINGS);
logger.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());
return;
}
final String email = arguments.get(0);
if (!validationService.validateEmail(email)) {
commonService.send(player, MessageKey.INVALID_EMAIL);
} else if (isSecondArgValidForEmailRegistration(player, arguments)) {
management.performRegister(RegistrationMethod.EMAIL_REGISTRATION,
EmailRegisterParams.of(player, email));
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (dataSource.getAuth(player.getName()) != null) {
if (dataSource.getAuth(player.getName()).getLastLogin() == null) {
management.performUnregisterByAdmin(null, player.getName(), player);
timer.cancel();
}
} else {
timer.cancel();
}
}
}, 600000);
}
}
/**
* Verifies that the second argument is valid (based on the configuration)
* to perform an email registration. The player is informed if the check
* is unsuccessful.
*
* @param player the player to register
* @param arguments the provided arguments
* @return true if valid, false otherwise
*/
private boolean isSecondArgValidForEmailRegistration(Player player, List<String> arguments) {
RegisterSecondaryArgument secondArgType = commonService.getProperty(REGISTER_SECOND_ARGUMENT);
// cases where args.size < 2
if (secondArgType == NONE || secondArgType == EMAIL_OPTIONAL && arguments.size() < 2) {
return true;
} else if (arguments.size() < 2) {
commonService.send(player, MessageKey.USAGE_REGISTER);
return false;
}
if (secondArgType == EMAIL_OPTIONAL || secondArgType == EMAIL_MANDATORY || secondArgType == CONFIRMATION) {
if (arguments.get(0).equals(arguments.get(1))) {
return true;
} else {
commonService.send(player, MessageKey.USAGE_REGISTER);
return false;
}
} else {
throw new IllegalStateException("Unknown secondary argument type '" + secondArgType + "'");
}
}
}

View File

@ -0,0 +1,43 @@
package fr.xephi.authme.command.executable.totp;
import fr.xephi.authme.command.PlayerCommand;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.security.totp.GenerateTotpService;
import fr.xephi.authme.security.totp.TotpAuthenticator.TotpGenerationResult;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Command for a player to enable TOTP.
*/
public class AddTotpCommand extends PlayerCommand {
@Inject
private GenerateTotpService generateTotpService;
@Inject
private PlayerCache playerCache;
@Inject
private Messages messages;
@Override
protected void runCommand(Player player, List<String> arguments) {
PlayerAuth auth = playerCache.getAuth(player.getName());
if (auth == null) {
messages.send(player, MessageKey.NOT_LOGGED_IN);
} else if (auth.getTotpKey() == null) {
TotpGenerationResult createdTotpInfo = generateTotpService.generateTotpKey(player);
messages.send(player, MessageKey.TWO_FACTOR_CREATE,
createdTotpInfo.getTotpKey(), createdTotpInfo.getAuthenticatorQrCodeUrl());
messages.send(player, MessageKey.TWO_FACTOR_CREATE_CONFIRMATION_REQUIRED);
} else {
messages.send(player, MessageKey.TWO_FACTOR_ALREADY_ENABLED);
}
}
}

View File

@ -0,0 +1,74 @@
package fr.xephi.authme.command.executable.totp;
import fr.xephi.authme.ConsoleLogger;
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.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.security.totp.GenerateTotpService;
import fr.xephi.authme.security.totp.TotpAuthenticator.TotpGenerationResult;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Command to enable TOTP by supplying the proper code as confirmation.
*/
public class ConfirmTotpCommand extends PlayerCommand {
private final ConsoleLogger logger = ConsoleLoggerFactory.get(ConfirmTotpCommand.class);
@Inject
private GenerateTotpService generateTotpService;
@Inject
private PlayerCache playerCache;
@Inject
private DataSource dataSource;
@Inject
private Messages messages;
@Override
protected void runCommand(Player player, List<String> arguments) {
PlayerAuth auth = playerCache.getAuth(player.getName());
if (auth == null) {
messages.send(player, MessageKey.NOT_LOGGED_IN);
} else if (auth.getTotpKey() != null) {
messages.send(player, MessageKey.TWO_FACTOR_ALREADY_ENABLED);
} else {
verifyTotpCodeConfirmation(player, auth, arguments.get(0));
}
}
private void verifyTotpCodeConfirmation(Player player, PlayerAuth auth, String inputTotpCode) {
final TotpGenerationResult totpDetails = generateTotpService.getGeneratedTotpKey(player);
if (totpDetails == null) {
messages.send(player, MessageKey.TWO_FACTOR_ENABLE_ERROR_NO_CODE);
} else {
boolean isCodeValid = generateTotpService.isTotpCodeCorrectForGeneratedTotpKey(player, inputTotpCode);
if (isCodeValid) {
generateTotpService.removeGenerateTotpKey(player);
insertTotpKeyIntoDatabase(player, auth, totpDetails);
} else {
messages.send(player, MessageKey.TWO_FACTOR_ENABLE_ERROR_WRONG_CODE);
}
}
}
private void insertTotpKeyIntoDatabase(Player player, PlayerAuth auth, TotpGenerationResult totpDetails) {
if (dataSource.setTotpKey(player.getName(), totpDetails.getTotpKey())) {
messages.send(player, MessageKey.TWO_FACTOR_ENABLE_SUCCESS);
auth.setTotpKey(totpDetails.getTotpKey());
playerCache.updatePlayer(auth);
logger.info("Player '" + player.getName() + "' has successfully added a TOTP key to their account");
} else {
messages.send(player, MessageKey.ERROR);
}
}
}

View File

@ -0,0 +1,62 @@
package fr.xephi.authme.command.executable.totp;
import fr.xephi.authme.ConsoleLogger;
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.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.security.totp.TotpAuthenticator;
import org.bukkit.entity.Player;
import javax.inject.Inject;
import java.util.List;
/**
* Command for a player to remove 2FA authentication.
*/
public class RemoveTotpCommand extends PlayerCommand {
private final ConsoleLogger logger = ConsoleLoggerFactory.get(RemoveTotpCommand.class);
@Inject
private DataSource dataSource;
@Inject
private PlayerCache playerCache;
@Inject
private TotpAuthenticator totpAuthenticator;
@Inject
private Messages messages;
@Override
protected void runCommand(Player player, List<String> arguments) {
PlayerAuth auth = playerCache.getAuth(player.getName());
if (auth == null) {
messages.send(player, MessageKey.NOT_LOGGED_IN);
} else if (auth.getTotpKey() == null) {
messages.send(player, MessageKey.TWO_FACTOR_NOT_ENABLED_ERROR);
} else {
if (totpAuthenticator.checkCode(auth, arguments.get(0))) {
removeTotpKeyFromDatabase(player, auth);
} else {
messages.send(player, MessageKey.TWO_FACTOR_INVALID_CODE);
}
}
}
private void removeTotpKeyFromDatabase(Player player, PlayerAuth auth) {
if (dataSource.removeTotpKey(auth.getNickname())) {
auth.setTotpKey(null);
playerCache.updatePlayer(auth);
messages.send(player, MessageKey.TWO_FACTOR_REMOVED_SUCCESS);
logger.info("Player '" + player.getName() + "' removed their TOTP key");
} else {
messages.send(player, MessageKey.ERROR);
}
}
}

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