mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2024-12-18 14:47:47 +01:00
Merge branch 'master' into ip-check
This commit is contained in:
commit
1a1ba31bf1
@ -1,49 +0,0 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build_and_test_jdk8:
|
||||
working_directory: ~/authmereloaded-jdk8
|
||||
docker:
|
||||
- image: circleci/openjdk:8-jdk
|
||||
environment:
|
||||
MAVEN_OPTS: -Xmx2048m
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- authmereloaded-{{ checksum "pom.xml" }}
|
||||
- authmereloaded-
|
||||
- run: mvn -T 2 dependency:go-offline
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
key: authmereloaded-{{ checksum "pom.xml" }}
|
||||
- run: mvn -T 2 package
|
||||
- store_test_results:
|
||||
path: target/surefire-reports
|
||||
- store_artifacts:
|
||||
path: target/*.jar
|
||||
build_and_test_jdk9:
|
||||
working_directory: ~/authmereloaded-jdk9
|
||||
docker:
|
||||
- image: circleci/openjdk:9-jdk
|
||||
environment:
|
||||
MAVEN_OPTS: -Xmx2048m
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: authmereloaded-{{ checksum "pom.xml" }}
|
||||
- run: mvn -T 2 dependency:go-offline
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
key: authmereloaded-{{ checksum "pom.xml" }}
|
||||
- run: mvn -T 2 package
|
||||
- store_test_results:
|
||||
path: target/surefire-reports
|
||||
- run: cp ./target/*.jar $CIRCLE_ARTIFACTS
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_test:
|
||||
jobs:
|
||||
- build_and_test_jdk8
|
||||
- build_and_test_jdk9
|
52
.circleci/config.yml
Normal file
52
.circleci/config.yml
Normal file
@ -0,0 +1,52 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build_and_test_jdk8:
|
||||
working_directory: ~/authmereloaded-jdk8
|
||||
docker:
|
||||
- image: circleci/openjdk:8-jdk
|
||||
environment:
|
||||
MAVEN_OPTS: -Xmx2048m
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- authmereloaded-{{ checksum "pom.xml" }}
|
||||
- authmereloaded-
|
||||
- run: mvn -T 2 -B dependency:go-offline
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
key: authmereloaded-{{ checksum "pom.xml" }}
|
||||
- run: mvn -T 2 -B package
|
||||
- store_test_results:
|
||||
path: target/surefire-reports
|
||||
- store_artifacts:
|
||||
path: target/*.jar
|
||||
build_and_test_jdk10:
|
||||
working_directory: ~/authmereloaded-jdk10
|
||||
docker:
|
||||
- image: circleci/openjdk:10-jdk
|
||||
environment:
|
||||
MAVEN_OPTS: -Xmx2048m
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- authmereloaded-{{ checksum "pom.xml" }}
|
||||
- authmereloaded-
|
||||
- run: mvn -T 2 -B dependency:go-offline
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
key: authmereloaded-{{ checksum "pom.xml" }}
|
||||
- run: mvn -T 2 -B package
|
||||
- store_test_results:
|
||||
path: target/surefire-reports
|
||||
- store_artifacts:
|
||||
path: target/*.jar
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_test:
|
||||
jobs:
|
||||
- build_and_test_jdk8
|
||||
- build_and_test_jdk10
|
@ -8,7 +8,6 @@
|
||||
| **Code quality:** | [![Code Climate](https://codeclimate.com/github/AuthMe/AuthMeReloaded/badges/gpa.svg)](https://codeclimate.com/github/AuthMe/AuthMeReloaded) [![Coverage status](https://coveralls.io/repos/AuthMe-Team/AuthMeReloaded/badge.svg?branch=master&service=github)](https://coveralls.io/github/AuthMe-Team/AuthMeReloaded?branch=master) |
|
||||
| **Jenkins CI:** | [![Jenkins Status](https://img.shields.io/website-up-down-green-red/http/shields.io.svg?label=ci.codemc.org)](https://ci.codemc.org/) [![Build Status](https://ci.codemc.org/buildStatus/icon?job=AuthMe/AuthMeReloaded)](https://ci.codemc.org/job/AuthMe/job/AuthMeReloaded) ![Build Tests](https://img.shields.io/jenkins/t/https/ci.codemc.org/job/AuthMe/job/AuthMeReloaded.svg) |
|
||||
| **Other CIs:** | [![CircleCI](https://circleci.com/gh/AuthMe/AuthMeReloaded.svg?style=svg)](https://circleci.com/gh/AuthMe/AuthMeReloaded) |
|
||||
| **Dependencies:** | [![Dependency Status](https://gemnasium.com/badges/github.com/AuthMe/AuthMeReloaded.svg)](https://gemnasium.com/github.com/AuthMe/AuthMeReloaded) |
|
||||
|
||||
## Description
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
|
||||
<!-- File auto-generated on Fri Feb 02 20:09:14 CET 2018. See docs/commands/commands.tpl.md -->
|
||||
<!-- File auto-generated on Sun Apr 22 11:00:10 CEST 2018. 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 `< >`
|
||||
@ -85,6 +85,15 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
|
||||
- **/changepassword** <oldPassword> <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** <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** <code>: Saves the generated TOTP secret after confirmation.
|
||||
<br />Requires `authme.player.totpadd`
|
||||
- **/totp remove** <code>: Disables two-factor authentication for your account.
|
||||
<br />Requires `authme.player.totpremove`
|
||||
- **/totp help** [query]: View detailed help for /totp commands.
|
||||
- **/captcha** <captcha>: Captcha command for AuthMeReloaded.
|
||||
<br />Requires `authme.player.captcha`
|
||||
- **/captcha help** [query]: View detailed help for /captcha commands.
|
||||
@ -95,4 +104,4 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
|
||||
|
||||
---
|
||||
|
||||
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Fri Feb 02 20:09:14 CET 2018
|
||||
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Apr 22 11:00:10 CEST 2018
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
|
||||
<!-- File auto-generated on Sun Jan 21 18:49:44 CET 2018. See docs/config/config.tpl.md -->
|
||||
<!-- File auto-generated on Mon May 21 09:08:25 CEST 2018. 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,
|
||||
@ -37,12 +37,16 @@ DataSource:
|
||||
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
|
||||
@ -69,8 +73,6 @@ DataSource:
|
||||
# You should set this at least 30 seconds less than mysql server wait_timeout
|
||||
maxLifetime: 1800
|
||||
ExternalBoardOptions:
|
||||
# Column for storing players passwords salts
|
||||
mySQLColumnSalt: ''
|
||||
# Column for storing players groups
|
||||
mySQLColumnGroup: ''
|
||||
# -1 means disabled. If you want that only activated players
|
||||
@ -138,6 +140,8 @@ settings:
|
||||
- '/reg'
|
||||
- '/email'
|
||||
- '/captcha'
|
||||
- '/2fa'
|
||||
- '/totp'
|
||||
# Max number of allowed registrations per IP
|
||||
# The value 0 means an unlimited number of registrations!
|
||||
maxRegPerIp: 1
|
||||
@ -384,7 +388,7 @@ Protection:
|
||||
# Apply the protection also to registered usernames
|
||||
enableProtectionRegistered: true
|
||||
# Countries allowed to join the server and register. For country codes, see
|
||||
# http://dev.maxmind.com/geoip/legacy/codes/iso3166/
|
||||
# https://dev.maxmind.com/geoip/legacy/codes/iso3166/
|
||||
# PLEASE USE QUOTES!
|
||||
countries:
|
||||
- 'US'
|
||||
@ -404,6 +408,9 @@ Protection:
|
||||
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
|
||||
@ -555,4 +562,4 @@ To change settings on a running server, save your changes to config.yml and use
|
||||
|
||||
---
|
||||
|
||||
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Jan 21 18:49:44 CET 2018
|
||||
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Mon May 21 09:08:25 CEST 2018
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
|
||||
<!-- File auto-generated on Fri Dec 01 19:16:17 CET 2017. See docs/permissions/permission_nodes.tpl.md -->
|
||||
<!-- File auto-generated on Mon May 21 08:43:08 CEST 2018. 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.
|
||||
@ -30,6 +30,7 @@ The following are the permission nodes that are currently supported by the lates
|
||||
- **authme.admin.switchantibot** – Administrator command to toggle the AntiBot protection status.
|
||||
- **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.bypasscountrycheck** – Permission to bypass the GeoIp country code check.
|
||||
@ -57,13 +58,16 @@ The following are the permission nodes that are currently supported by the lates
|
||||
- **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 Fri Dec 01 19:16:17 CET 2017
|
||||
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Mon May 21 08:43:08 CEST 2018
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
|
||||
<!-- File auto-generated on Fri Feb 02 20:09:17 CET 2018. See docs/translations/translations.tpl.md -->
|
||||
<!-- File auto-generated on Mon Jun 25 21:53:35 CEST 2018. See docs/translations/translations.tpl.md -->
|
||||
|
||||
# AuthMe Translations
|
||||
The following translations are available in AuthMe. Set `messagesLanguage` to the language code
|
||||
@ -8,37 +8,37 @@ in your config.yml to use the language, or use another language code to start a
|
||||
Code | Language | Translated |
|
||||
---- | -------- | ---------: | ------
|
||||
[en](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_en.yml) | English | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
|
||||
[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 86% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=86&h=5&txtpad=1" alt="bar" />
|
||||
[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 90% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=90&h=5&txtpad=1" alt="bar" />
|
||||
[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 90% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=90&h=5&txtpad=1" alt="bar" />
|
||||
[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 90% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=90&h=5&txtpad=1" alt="bar" />
|
||||
[eo](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eo.yml) | Esperanto | 90% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=90&h=5&txtpad=1" alt="bar" />
|
||||
[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
|
||||
[et](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_et.yml) | Estonian | 90% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=90&h=5&txtpad=1" alt="bar" />
|
||||
[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 48% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa5500&w=48&h=5&txtpad=1" alt="bar" />
|
||||
[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 51% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb6600&w=51&h=5&txtpad=1" alt="bar" />
|
||||
[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
|
||||
[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 54% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb6600&w=54&h=5&txtpad=1" alt="bar" />
|
||||
[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 98% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ee55&w=98&h=5&txtpad=1" alt="bar" />
|
||||
[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 53% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb6600&w=53&h=5&txtpad=1" alt="bar" />
|
||||
[bg](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_bg.yml) | Bulgarian | 76% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb9900&w=76&h=5&txtpad=1" alt="bar" />
|
||||
[br](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_br.yml) | Brazilian | 80% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=80&h=5&txtpad=1" alt="bar" />
|
||||
[cz](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_cz.yml) | Czech | 80% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=80&h=5&txtpad=1" alt="bar" />
|
||||
[de](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_de.yml) | German | 80% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=80&h=5&txtpad=1" alt="bar" />
|
||||
[eo](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eo.yml) | Esperanto | 80% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=80&h=5&txtpad=1" alt="bar" />
|
||||
[es](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_es.yml) | Spanish | 92% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=92&h=5&txtpad=1" alt="bar" />
|
||||
[et](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_et.yml) | Estonian | 80% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=80&h=5&txtpad=1" alt="bar" />
|
||||
[eu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_eu.yml) | Basque | 42% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa5500&w=42&h=5&txtpad=1" alt="bar" />
|
||||
[fi](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fi.yml) | Finnish | 45% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa5500&w=45&h=5&txtpad=1" alt="bar" />
|
||||
[fr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_fr.yml) | French | 89% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=89&h=5&txtpad=1" alt="bar" />
|
||||
[gl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_gl.yml) | Galician | 48% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa5500&w=48&h=5&txtpad=1" alt="bar" />
|
||||
[hu](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_hu.yml) | Hungarian | 87% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=87&h=5&txtpad=1" alt="bar" />
|
||||
[id](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_id.yml) | Indonesian | 47% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa5500&w=47&h=5&txtpad=1" alt="bar" />
|
||||
[it](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_it.yml) | Italian | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
|
||||
[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 98% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ee55&w=98&h=5&txtpad=1" alt="bar" />
|
||||
[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 40% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa4400&w=40&h=5&txtpad=1" alt="bar" />
|
||||
[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 90% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=90&h=5&txtpad=1" alt="bar" />
|
||||
[ko](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ko.yml) | Korean | 89% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=89&h=5&txtpad=1" alt="bar" />
|
||||
[lt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_lt.yml) | Lithuanian | 36% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aa4400&w=36&h=5&txtpad=1" alt="bar" />
|
||||
[nl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_nl.yml) | Dutch | 80% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=80&h=5&txtpad=1" alt="bar" />
|
||||
[pl](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pl.yml) | Polish | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
|
||||
[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 90% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=90&h=5&txtpad=1" alt="bar" />
|
||||
[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 90% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=90&h=5&txtpad=1" alt="bar" />
|
||||
[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 98% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ee55&w=98&h=5&txtpad=1" alt="bar" />
|
||||
[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 90% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=90&h=5&txtpad=1" alt="bar" />
|
||||
[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 86% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=86&h=5&txtpad=1" alt="bar" />
|
||||
[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 71% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=71&h=5&txtpad=1" alt="bar" />
|
||||
[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 87% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=87&h=5&txtpad=1" alt="bar" />
|
||||
[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
|
||||
[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 90% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=90&h=5&txtpad=1" alt="bar" />
|
||||
[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 73% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb8800&w=73&h=5&txtpad=1" alt="bar" />
|
||||
[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 98% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ee55&w=98&h=5&txtpad=1" alt="bar" />
|
||||
[pt](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_pt.yml) | Portuguese | 100% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=66ff66&w=100&h=5&txtpad=1" alt="bar" />
|
||||
[ro](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ro.yml) | Romanian | 80% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=80&h=5&txtpad=1" alt="bar" />
|
||||
[ru](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_ru.yml) | Russian | 92% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=77dd44&w=92&h=5&txtpad=1" alt="bar" />
|
||||
[sk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_sk.yml) | Slovakian | 80% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=80&h=5&txtpad=1" alt="bar" />
|
||||
[tr](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_tr.yml) | Turkish | 76% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb9900&w=76&h=5&txtpad=1" alt="bar" />
|
||||
[uk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_uk.yml) | Ukrainian | 63% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=63&h=5&txtpad=1" alt="bar" />
|
||||
[vn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_vn.yml) | Vietnamese | 77% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb9900&w=77&h=5&txtpad=1" alt="bar" />
|
||||
[zhcn](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhcn.yml) | Chinese (China) | 89% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=88cc33&w=89&h=5&txtpad=1" alt="bar" />
|
||||
[zhhk](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhhk.yml) | Chinese (Hong Kong) | 80% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=aaaa11&w=80&h=5&txtpad=1" alt="bar" />
|
||||
[zhmc](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhmc.yml) | Chinese (Macau) | 65% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=bb7700&w=65&h=5&txtpad=1" alt="bar" />
|
||||
[zhtw](https://github.com/AuthMe/AuthMeReloaded/blob/master/src/main/resources/messages/messages_zhtw.yml) | Chinese (Taiwan) | 87% | <img src="https://placeholdit.imgix.net/~text?txtsize=5&bg=99bb22&w=87&h=5&txtpad=1" alt="bar" />
|
||||
|
||||
|
||||
---
|
||||
|
||||
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Fri Feb 02 20:09:17 CET 2018
|
||||
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Mon Jun 25 21:53:35 CEST 2018
|
||||
|
64
pom.xml
64
pom.xml
@ -73,9 +73,13 @@
|
||||
<bukkitplugin.authors>Xephi, sgdc3, DNx5, timvisee, games647, ljacqu, Gnat008</bukkitplugin.authors>
|
||||
|
||||
<!-- Change Bukkit Version HERE! -->
|
||||
<bukkit.version>1.12.2-R0.1-SNAPSHOT</bukkit.version>
|
||||
<bukkit.version>1.13-pre7-R0.1-SNAPSHOT</bukkit.version>
|
||||
</properties>
|
||||
|
||||
<prerequisites>
|
||||
<maven>3.3.9</maven>
|
||||
</prerequisites>
|
||||
|
||||
<!-- Jenkins profile -->
|
||||
<profiles>
|
||||
<!-- Set the buildNumber using the jenkins env. variable -->
|
||||
@ -131,12 +135,12 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<version>3.1.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<version>3.1.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@ -150,7 +154,7 @@
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.0</version>
|
||||
<version>0.8.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>pre-unit-test</id>
|
||||
@ -169,10 +173,11 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.20.1</version>
|
||||
<version>2.21.0</version>
|
||||
<configuration>
|
||||
<!-- Force the right file encoding during unit testing -->
|
||||
<argLine>-Dfile.encoding=${project.build.sourceEncoding} @{argLine}</argLine>
|
||||
<!-- Set language to English in order to get consistent results for localized time formatting -->
|
||||
<argLine>-Dfile.encoding=${project.build.sourceEncoding} -Duser.language=en @{argLine}</argLine>
|
||||
<systemPropertyVariables>
|
||||
<project.skipExtendedHashTests>${project.skipExtendedHashTests}</project.skipExtendedHashTests>
|
||||
</systemPropertyVariables>
|
||||
@ -181,12 +186,12 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<version>3.1.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<version>3.0.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadoc</id>
|
||||
@ -226,7 +231,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.1.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
@ -251,12 +256,8 @@
|
||||
<shadedPattern>fr.xephi.authme.libs.com.google</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>ch.jalu.injector</pattern>
|
||||
<shadedPattern>fr.xephi.authme.libs.jalu.injector</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>ch.jalu.configme</pattern>
|
||||
<shadedPattern>fr.xephi.authme.libs.ch.jalu.configme</shadedPattern>
|
||||
<pattern>ch.jalu</pattern>
|
||||
<shadedPattern>fr.xephi.authme.libs.ch.jalu</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.zaxxer.hikari</pattern>
|
||||
@ -286,6 +287,10 @@
|
||||
<pattern>de.mkammerer</pattern>
|
||||
<shadedPattern>fr.xephi.authme.libs.de.mkammerer</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.warrenstrange</pattern>
|
||||
<shadedPattern>fr.xephi.authme.libs.com.warrenstrange</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>javax.inject</pattern>
|
||||
<shadedPattern>fr.xephi.authme.libs.javax.inject</shadedPattern>
|
||||
@ -387,7 +392,7 @@
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.8.2</version>
|
||||
<version>2.8.5</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@ -395,7 +400,7 @@
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>24.1-jre</version>
|
||||
<version>25.1-jre</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@ -436,7 +441,7 @@
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>2.7.8</version>
|
||||
<version>3.2.0</version>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@ -469,6 +474,14 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- TOTP client -->
|
||||
<dependency>
|
||||
<groupId>com.warrenstrange</groupId>
|
||||
<artifactId>googleauth</artifactId>
|
||||
<version>1.1.5</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Spigot API, http://www.spigotmc.org/ -->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
@ -545,7 +558,7 @@
|
||||
<dependency>
|
||||
<groupId>me.lucko.luckperms</groupId>
|
||||
<artifactId>luckperms-api</artifactId>
|
||||
<version>4.1</version>
|
||||
<version>4.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@ -788,6 +801,13 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.jalu</groupId>
|
||||
<artifactId>datasourcecolumns</artifactId>
|
||||
<version>0.1.1-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Unit Testing Libraries -->
|
||||
|
||||
<dependency>
|
||||
@ -808,7 +828,7 @@
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
<version>2.16.0</version>
|
||||
<version>2.19.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
@ -821,13 +841,13 @@
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.21.0.1</version>
|
||||
<version>3.23.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.196</version>
|
||||
<version>1.4.197</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
@ -2,9 +2,7 @@ package fr.xephi.authme;
|
||||
|
||||
import ch.jalu.injector.Injector;
|
||||
import ch.jalu.injector.InjectorBuilder;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import fr.xephi.authme.api.NewAPI;
|
||||
import fr.xephi.authme.command.CommandHandler;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
@ -35,9 +33,6 @@ 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 java.io.File;
|
||||
|
||||
import org.apache.commons.lang.SystemUtils;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.Command;
|
||||
@ -48,6 +43,8 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.plugin.java.JavaPluginLoader;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static fr.xephi.authme.service.BukkitService.TICKS_PER_MINUTE;
|
||||
import static fr.xephi.authme.util.Utils.isClassLoaded;
|
||||
|
||||
@ -160,7 +157,7 @@ public class AuthMe extends JavaPlugin {
|
||||
|
||||
// Sponsor messages
|
||||
ConsoleLogger.info("Development builds are available on our jenkins, thanks to FastVM.io");
|
||||
ConsoleLogger.info("Do you want a good vps for your game server? Look at our sponsor FastVM.io leader "
|
||||
ConsoleLogger.info("Do you want a good vps for your game server? Look at our sponsor FastVM.io leader "
|
||||
+ "as virtual server provider!");
|
||||
|
||||
// Successful message
|
||||
|
@ -5,7 +5,7 @@ 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.StringUtils;
|
||||
import fr.xephi.authme.util.ExceptionUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
@ -101,7 +101,7 @@ public final class ConsoleLogger {
|
||||
* @param th The Throwable to log
|
||||
*/
|
||||
public static void logException(String message, Throwable th) {
|
||||
warning(message + " " + StringUtils.formatException(th));
|
||||
warning(message + " " + ExceptionUtils.formatException(th));
|
||||
writeLog(Throwables.getStackTraceAsString(th));
|
||||
}
|
||||
|
||||
|
@ -33,13 +33,18 @@ 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.SetPasswordCommand;
|
||||
import fr.xephi.authme.command.executable.email.ShowEmailCommand;
|
||||
import fr.xephi.authme.command.executable.login.LoginCommand;
|
||||
import fr.xephi.authme.command.executable.logout.LogoutCommand;
|
||||
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;
|
||||
@ -55,6 +60,9 @@ import java.util.List;
|
||||
*/
|
||||
public class CommandInitializer {
|
||||
|
||||
private static final boolean OPTIONAL = true;
|
||||
private static final boolean MANDATORY = false;
|
||||
|
||||
private List<CommandDescription> commands;
|
||||
|
||||
public CommandInitializer() {
|
||||
@ -84,7 +92,7 @@ public class CommandInitializer {
|
||||
.labels("login", "l", "log")
|
||||
.description("Login command")
|
||||
.detailedDescription("Command to log in using AuthMeReloaded.")
|
||||
.withArgument("password", "Login password", false)
|
||||
.withArgument("password", "Login password", MANDATORY)
|
||||
.permission(PlayerPermission.LOGIN)
|
||||
.executableCommand(LoginCommand.class)
|
||||
.register();
|
||||
@ -105,8 +113,8 @@ public class CommandInitializer {
|
||||
.labels("register", "reg")
|
||||
.description("Register an account")
|
||||
.detailedDescription("Command to register using AuthMeReloaded.")
|
||||
.withArgument("password", "Password", true)
|
||||
.withArgument("verifyPassword", "Verify password", true)
|
||||
.withArgument("password", "Password", OPTIONAL)
|
||||
.withArgument("verifyPassword", "Verify password", OPTIONAL)
|
||||
.permission(PlayerPermission.REGISTER)
|
||||
.executableCommand(RegisterCommand.class)
|
||||
.register();
|
||||
@ -117,7 +125,7 @@ public class CommandInitializer {
|
||||
.labels("unregister", "unreg")
|
||||
.description("Unregister an account")
|
||||
.detailedDescription("Command to unregister using AuthMeReloaded.")
|
||||
.withArgument("password", "Password", false)
|
||||
.withArgument("password", "Password", MANDATORY)
|
||||
.permission(PlayerPermission.UNREGISTER)
|
||||
.executableCommand(UnregisterCommand.class)
|
||||
.register();
|
||||
@ -128,19 +136,22 @@ public class CommandInitializer {
|
||||
.labels("changepassword", "changepass", "cp")
|
||||
.description("Change password of an account")
|
||||
.detailedDescription("Command to change your password using AuthMeReloaded.")
|
||||
.withArgument("oldPassword", "Old password", false)
|
||||
.withArgument("newPassword", "New password", false)
|
||||
.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", false)
|
||||
.withArgument("captcha", "The Captcha", MANDATORY)
|
||||
.permission(PlayerPermission.CAPTCHA)
|
||||
.executableCommand(CaptchaCommand.class)
|
||||
.register();
|
||||
@ -151,21 +162,13 @@ public class CommandInitializer {
|
||||
.labels("verification")
|
||||
.description("Verification command")
|
||||
.detailedDescription("Command to complete the verification process for AuthMeReloaded.")
|
||||
.withArgument("code", "The code", false)
|
||||
.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,
|
||||
captchaBase,
|
||||
verificationBase);
|
||||
List<CommandDescription> baseCommands = ImmutableList.of(authMeBase, emailBase, loginBase, logoutBase,
|
||||
registerBase, unregisterBase, changePasswordBase, totpBase, captchaBase, verificationBase);
|
||||
|
||||
setHelpOnAllBases(baseCommands);
|
||||
commands = baseCommands;
|
||||
@ -191,8 +194,8 @@ public class CommandInitializer {
|
||||
.labels("register", "reg", "r")
|
||||
.description("Register a player")
|
||||
.detailedDescription("Register the specified player with the specified password.")
|
||||
.withArgument("player", "Player name", false)
|
||||
.withArgument("password", "Password", false)
|
||||
.withArgument("player", "Player name", MANDATORY)
|
||||
.withArgument("password", "Password", MANDATORY)
|
||||
.permission(AdminPermission.REGISTER)
|
||||
.executableCommand(RegisterAdminCommand.class)
|
||||
.register();
|
||||
@ -203,7 +206,7 @@ public class CommandInitializer {
|
||||
.labels("unregister", "unreg", "unr")
|
||||
.description("Unregister a player")
|
||||
.detailedDescription("Unregister the specified player.")
|
||||
.withArgument("player", "Player name", false)
|
||||
.withArgument("player", "Player name", MANDATORY)
|
||||
.permission(AdminPermission.UNREGISTER)
|
||||
.executableCommand(UnregisterAdminCommand.class)
|
||||
.register();
|
||||
@ -214,7 +217,7 @@ public class CommandInitializer {
|
||||
.labels("forcelogin", "login")
|
||||
.description("Enforce login player")
|
||||
.detailedDescription("Enforce the specified player to login.")
|
||||
.withArgument("player", "Online player name", true)
|
||||
.withArgument("player", "Online player name", OPTIONAL)
|
||||
.permission(AdminPermission.FORCE_LOGIN)
|
||||
.executableCommand(ForceLoginCommand.class)
|
||||
.register();
|
||||
@ -225,8 +228,8 @@ public class CommandInitializer {
|
||||
.labels("password", "changepassword", "changepass", "cp")
|
||||
.description("Change a player's password")
|
||||
.detailedDescription("Change the password of a player.")
|
||||
.withArgument("player", "Player name", false)
|
||||
.withArgument("pwd", "New password", false)
|
||||
.withArgument("player", "Player name", MANDATORY)
|
||||
.withArgument("pwd", "New password", MANDATORY)
|
||||
.permission(AdminPermission.CHANGE_PASSWORD)
|
||||
.executableCommand(ChangePasswordAdminCommand.class)
|
||||
.register();
|
||||
@ -237,7 +240,7 @@ public class CommandInitializer {
|
||||
.labels("lastlogin", "ll")
|
||||
.description("Player's last login")
|
||||
.detailedDescription("View the date of the specified players last login.")
|
||||
.withArgument("player", "Player name", true)
|
||||
.withArgument("player", "Player name", OPTIONAL)
|
||||
.permission(AdminPermission.LAST_LOGIN)
|
||||
.executableCommand(LastLoginCommand.class)
|
||||
.register();
|
||||
@ -248,7 +251,7 @@ public class CommandInitializer {
|
||||
.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", true)
|
||||
.withArgument("player", "Player name or IP", OPTIONAL)
|
||||
.permission(AdminPermission.ACCOUNTS)
|
||||
.executableCommand(AccountsCommand.class)
|
||||
.register();
|
||||
@ -259,7 +262,7 @@ public class CommandInitializer {
|
||||
.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", true)
|
||||
.withArgument("player", "Player name", OPTIONAL)
|
||||
.permission(AdminPermission.GET_EMAIL)
|
||||
.executableCommand(GetEmailCommand.class)
|
||||
.register();
|
||||
@ -270,8 +273,8 @@ public class CommandInitializer {
|
||||
.labels("setemail", "setmail", "chgemail", "chgmail")
|
||||
.description("Change player's email")
|
||||
.detailedDescription("Change the email address of the specified player.")
|
||||
.withArgument("player", "Player name", false)
|
||||
.withArgument("email", "Player email", false)
|
||||
.withArgument("player", "Player name", MANDATORY)
|
||||
.withArgument("email", "Player email", MANDATORY)
|
||||
.permission(AdminPermission.CHANGE_EMAIL)
|
||||
.executableCommand(SetEmailCommand.class)
|
||||
.register();
|
||||
@ -282,7 +285,7 @@ public class CommandInitializer {
|
||||
.labels("getip", "ip")
|
||||
.description("Get player's IP")
|
||||
.detailedDescription("Get the IP address of the specified online player.")
|
||||
.withArgument("player", "Player name", false)
|
||||
.withArgument("player", "Player name", MANDATORY)
|
||||
.permission(AdminPermission.GET_IP)
|
||||
.executableCommand(GetIpCommand.class)
|
||||
.register();
|
||||
@ -333,7 +336,7 @@ public class CommandInitializer {
|
||||
.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", false)
|
||||
.withArgument("days", "Number of days", MANDATORY)
|
||||
.permission(AdminPermission.PURGE)
|
||||
.executableCommand(PurgeCommand.class)
|
||||
.register();
|
||||
@ -344,8 +347,8 @@ public class CommandInitializer {
|
||||
.labels("purgeplayer")
|
||||
.description("Purges the data of one player")
|
||||
.detailedDescription("Purges data of the given player.")
|
||||
.withArgument("player", "The player to purge", false)
|
||||
.withArgument("options", "'force' to run without checking if player is registered", true)
|
||||
.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();
|
||||
@ -367,7 +370,7 @@ public class CommandInitializer {
|
||||
"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", false)
|
||||
.withArgument("player/*", "Player name or * for all players", MANDATORY)
|
||||
.permission(AdminPermission.PURGE_LAST_POSITION)
|
||||
.executableCommand(PurgeLastPositionCommand.class)
|
||||
.register();
|
||||
@ -388,7 +391,7 @@ public class CommandInitializer {
|
||||
.labels("switchantibot", "toggleantibot", "antibot")
|
||||
.description("Switch AntiBot mode")
|
||||
.detailedDescription("Switch or toggle the AntiBot mode to the specified state.")
|
||||
.withArgument("mode", "ON / OFF", true)
|
||||
.withArgument("mode", "ON / OFF", OPTIONAL)
|
||||
.permission(AdminPermission.SWITCH_ANTIBOT)
|
||||
.executableCommand(SwitchAntiBotCommand.class)
|
||||
.register();
|
||||
@ -419,7 +422,7 @@ public class CommandInitializer {
|
||||
.description("Converter command")
|
||||
.detailedDescription("Converter command for AuthMeReloaded.")
|
||||
.withArgument("job", "Conversion job: xauth / crazylogin / rakamak / "
|
||||
+ "royalauth / vauth / sqliteToSql / mysqlToSqlite / loginsecurity", true)
|
||||
+ "royalauth / vauth / sqliteToSql / mysqlToSqlite / loginsecurity", OPTIONAL)
|
||||
.permission(AdminPermission.CONVERTER)
|
||||
.executableCommand(ConverterCommand.class)
|
||||
.register();
|
||||
@ -447,9 +450,9 @@ public class CommandInitializer {
|
||||
.labels("debug", "dbg")
|
||||
.description("Debug features")
|
||||
.detailedDescription("Allows various operations for debugging.")
|
||||
.withArgument("child", "The child to execute", true)
|
||||
.withArgument("arg", "argument (depends on debug section)", true)
|
||||
.withArgument("arg", "argument (depends on debug section)", true)
|
||||
.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();
|
||||
@ -488,8 +491,8 @@ public class CommandInitializer {
|
||||
.labels("add", "addemail", "addmail")
|
||||
.description("Add Email")
|
||||
.detailedDescription("Add a new email address to your account.")
|
||||
.withArgument("email", "Email address", false)
|
||||
.withArgument("verifyEmail", "Email address verification", false)
|
||||
.withArgument("email", "Email address", MANDATORY)
|
||||
.withArgument("verifyEmail", "Email address verification", MANDATORY)
|
||||
.permission(PlayerPermission.ADD_EMAIL)
|
||||
.executableCommand(AddEmailCommand.class)
|
||||
.register();
|
||||
@ -500,8 +503,8 @@ public class CommandInitializer {
|
||||
.labels("change", "changeemail", "changemail")
|
||||
.description("Change Email")
|
||||
.detailedDescription("Change an email address of your account.")
|
||||
.withArgument("oldEmail", "Old email address", false)
|
||||
.withArgument("newEmail", "New email address", false)
|
||||
.withArgument("oldEmail", "Old email address", MANDATORY)
|
||||
.withArgument("newEmail", "New email address", MANDATORY)
|
||||
.permission(PlayerPermission.CHANGE_EMAIL)
|
||||
.executableCommand(ChangeEmailCommand.class)
|
||||
.register();
|
||||
@ -513,7 +516,7 @@ public class CommandInitializer {
|
||||
.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", false)
|
||||
.withArgument("email", "Email address", MANDATORY)
|
||||
.permission(PlayerPermission.RECOVER_EMAIL)
|
||||
.executableCommand(RecoverEmailCommand.class)
|
||||
.register();
|
||||
@ -524,7 +527,7 @@ public class CommandInitializer {
|
||||
.labels("code")
|
||||
.description("Submit code to recover password")
|
||||
.detailedDescription("Recover your account by submitting a code delivered to your email.")
|
||||
.withArgument("code", "Recovery code", false)
|
||||
.withArgument("code", "Recovery code", MANDATORY)
|
||||
.permission(PlayerPermission.RECOVER_EMAIL)
|
||||
.executableCommand(ProcessCodeCommand.class)
|
||||
.register();
|
||||
@ -535,14 +538,74 @@ public class CommandInitializer {
|
||||
.labels("setpassword")
|
||||
.description("Set new password after recovery")
|
||||
.detailedDescription("Set a new password after successfully recovering your account.")
|
||||
.withArgument("password", "New password", false)
|
||||
.withArgument("password", "New password", MANDATORY)
|
||||
.permission(PlayerPermission.RECOVER_EMAIL)
|
||||
.executableCommand(SetPasswordCommand.class)
|
||||
.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.
|
||||
*
|
||||
@ -557,7 +620,7 @@ public class CommandInitializer {
|
||||
.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.", true)
|
||||
.withArgument("query", "The command or query to view help for.", OPTIONAL)
|
||||
.executableCommand(HelpCommand.class)
|
||||
.register();
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
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.datasource.DataSourceResult;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.service.CommonService;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@ -25,8 +25,8 @@ public class GetEmailCommand implements ExecutableCommand {
|
||||
public void executeCommand(CommandSender sender, List<String> arguments) {
|
||||
String playerName = arguments.isEmpty() ? sender.getName() : arguments.get(0);
|
||||
|
||||
DataSourceResult<String> email = dataSource.getEmail(playerName);
|
||||
if (email.playerExists()) {
|
||||
DataSourceValue<String> email = dataSource.getEmail(playerName);
|
||||
if (email.rowExists()) {
|
||||
sender.sendMessage("[AuthMe] " + playerName + "'s email: " + email.getValue());
|
||||
} else {
|
||||
commonService.send(sender, MessageKey.UNKNOWN_USER);
|
||||
|
@ -1,5 +1,6 @@
|
||||
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;
|
||||
@ -26,10 +27,8 @@ public class PurgeCommand implements ExecutableCommand {
|
||||
String daysStr = arguments.get(0);
|
||||
|
||||
// Convert the days string to an integer value, and make sure it's valid
|
||||
int days;
|
||||
try {
|
||||
days = Integer.parseInt(daysStr);
|
||||
} catch (NumberFormatException ex) {
|
||||
Integer days = Ints.tryParse(daysStr);
|
||||
if (days == null) {
|
||||
sender.sendMessage(ChatColor.RED + "The value you've entered is invalid!");
|
||||
return;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ 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;
|
||||
|
||||
@ -24,8 +25,8 @@ public class UpdateHelpMessagesCommand implements ExecutableCommand {
|
||||
@Override
|
||||
public void executeCommand(CommandSender sender, List<String> arguments) {
|
||||
try {
|
||||
helpTranslationGenerator.updateHelpFile();
|
||||
sender.sendMessage("Successfully updated the help file");
|
||||
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());
|
||||
|
@ -77,6 +77,7 @@ class PlayerAuthViewer implements DebugSection {
|
||||
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) + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,8 +1,8 @@
|
||||
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.datasource.DataSourceResult;
|
||||
import fr.xephi.authme.mail.SendMailSsl;
|
||||
import fr.xephi.authme.permission.DebugSectionPermissions;
|
||||
import fr.xephi.authme.permission.PermissionNode;
|
||||
@ -82,8 +82,8 @@ class TestEmailSender implements DebugSection {
|
||||
*/
|
||||
private String getEmail(CommandSender sender, List<String> arguments) {
|
||||
if (arguments.isEmpty()) {
|
||||
DataSourceResult<String> emailResult = dataSource.getEmail(sender.getName());
|
||||
if (!emailResult.playerExists()) {
|
||||
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;
|
||||
|
@ -4,6 +4,7 @@ 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;
|
||||
@ -80,6 +81,6 @@ public class CaptchaCommand extends PlayerCommand {
|
||||
String newCode = registrationCaptchaManager.getCaptchaCodeOrGenerateNew(player.getName());
|
||||
commonService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, newCode);
|
||||
}
|
||||
limboService.resetMessageTask(player, false);
|
||||
limboService.resetMessageTask(player, LimboMessageType.REGISTER);
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import java.util.List;
|
||||
/**
|
||||
* Command for changing password following successful recovery.
|
||||
*/
|
||||
public class SetPasswordCommand extends PlayerCommand {
|
||||
public class EmailSetPasswordCommand extends PlayerCommand {
|
||||
|
||||
@Inject
|
||||
private DataSource dataSource;
|
||||
@ -45,11 +45,14 @@ public class SetPasswordCommand extends PlayerCommand {
|
||||
if (!result.hasError()) {
|
||||
HashedPassword hashedPassword = passwordSecurity.computeHash(password, name);
|
||||
dataSource.updatePassword(name, hashedPassword);
|
||||
recoveryService.removeFromSuccessfulRecovery(player);
|
||||
ConsoleLogger.info("Player '" + name + "' has changed their password from recovery");
|
||||
commonService.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS);
|
||||
} else {
|
||||
commonService.send(player, result.getMessageKey(), result.getArgs());
|
||||
}
|
||||
} else {
|
||||
commonService.send(player, MessageKey.CHANGE_PASSWORD_EXPIRED);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
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.datasource.DataSourceResult;
|
||||
import fr.xephi.authme.mail.EmailService;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
@ -58,8 +58,8 @@ public class RecoverEmailCommand extends PlayerCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
DataSourceResult<String> emailResult = dataSource.getEmail(playerName);
|
||||
if (!emailResult.playerExists()) {
|
||||
DataSourceValue<String> emailResult = dataSource.getEmail(playerName);
|
||||
if (!emailResult.rowExists()) {
|
||||
commonService.send(player, MessageKey.USAGE_REGISTER);
|
||||
return;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ public class LoginCommand extends PlayerCommand {
|
||||
|
||||
@Override
|
||||
public void runCommand(Player player, List<String> arguments) {
|
||||
final String password = arguments.get(0);
|
||||
String password = arguments.get(0);
|
||||
management.performLogin(player, password);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
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.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 {
|
||||
|
||||
@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);
|
||||
ConsoleLogger.info("Player '" + player.getName() + "' has successfully added a TOTP key to their account");
|
||||
} else {
|
||||
messages.send(player, MessageKey.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
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.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 {
|
||||
|
||||
@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);
|
||||
ConsoleLogger.info("Player '" + player.getName() + "' removed their TOTP key");
|
||||
} else {
|
||||
messages.send(player, MessageKey.ERROR);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package fr.xephi.authme.command.executable.totp;
|
||||
|
||||
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 /totp.
|
||||
*/
|
||||
public class TotpBaseCommand 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("totp"));
|
||||
helpProvider.outputHelp(sender, result, HelpProvider.SHOW_CHILDREN);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
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.data.limbo.LimboPlayer;
|
||||
import fr.xephi.authme.data.limbo.LimboPlayerState;
|
||||
import fr.xephi.authme.data.limbo.LimboService;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.message.Messages;
|
||||
import fr.xephi.authme.process.login.AsynchronousLogin;
|
||||
import fr.xephi.authme.security.totp.TotpAuthenticator;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TOTP code command for processing the 2FA code during the login process.
|
||||
*/
|
||||
public class TotpCodeCommand extends PlayerCommand {
|
||||
|
||||
@Inject
|
||||
private LimboService limboService;
|
||||
|
||||
@Inject
|
||||
private PlayerCache playerCache;
|
||||
|
||||
@Inject
|
||||
private Messages messages;
|
||||
|
||||
@Inject
|
||||
private TotpAuthenticator totpAuthenticator;
|
||||
|
||||
@Inject
|
||||
private DataSource dataSource;
|
||||
|
||||
@Inject
|
||||
private AsynchronousLogin asynchronousLogin;
|
||||
|
||||
@Override
|
||||
protected void runCommand(Player player, List<String> arguments) {
|
||||
if (playerCache.isAuthenticated(player.getName())) {
|
||||
messages.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerAuth auth = dataSource.getAuth(player.getName());
|
||||
if (auth == null) {
|
||||
messages.send(player, MessageKey.REGISTER_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
LimboPlayer limbo = limboService.getLimboPlayer(player.getName());
|
||||
if (limbo != null && limbo.getState() == LimboPlayerState.TOTP_REQUIRED) {
|
||||
processCode(player, auth, arguments.get(0));
|
||||
} else {
|
||||
ConsoleLogger.debug(() -> "Aborting TOTP check for player '" + player.getName()
|
||||
+ "'. Invalid limbo state: " + (limbo == null ? "no limbo" : limbo.getState()));
|
||||
messages.send(player, MessageKey.LOGIN_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void processCode(Player player, PlayerAuth auth, String inputCode) {
|
||||
boolean isCodeValid = totpAuthenticator.checkCode(auth, inputCode);
|
||||
if (isCodeValid) {
|
||||
ConsoleLogger.debug("Successfully checked TOTP code for `{0}`", player.getName());
|
||||
asynchronousLogin.performLogin(player, auth);
|
||||
} else {
|
||||
ConsoleLogger.debug("Input TOTP code was invalid for player `{0}`", player.getName());
|
||||
messages.send(player, MessageKey.TWO_FACTOR_INVALID_CODE);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package fr.xephi.authme.data;
|
||||
|
||||
import ch.jalu.datasourcecolumns.data.DataSourceValue;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.datasource.DataSourceResult;
|
||||
import fr.xephi.authme.initialization.HasCleanup;
|
||||
import fr.xephi.authme.initialization.SettingsDependent;
|
||||
import fr.xephi.authme.mail.EmailService;
|
||||
@ -103,8 +103,8 @@ public class VerificationCodeManager implements SettingsDependent, HasCleanup {
|
||||
*/
|
||||
public boolean hasEmail(String name) {
|
||||
boolean result = false;
|
||||
DataSourceResult<String> emailResult = dataSource.getEmail(name);
|
||||
if (emailResult.playerExists()) {
|
||||
DataSourceValue<String> emailResult = dataSource.getEmail(name);
|
||||
if (emailResult.rowExists()) {
|
||||
final String email = emailResult.getValue();
|
||||
if (!Utils.isEmailEmpty(email)) {
|
||||
result = true;
|
||||
@ -130,8 +130,8 @@ public class VerificationCodeManager implements SettingsDependent, HasCleanup {
|
||||
* @param name the name of the player to generate a code for
|
||||
*/
|
||||
private void generateCode(String name) {
|
||||
DataSourceResult<String> emailResult = dataSource.getEmail(name);
|
||||
if (emailResult.playerExists()) {
|
||||
DataSourceValue<String> emailResult = dataSource.getEmail(name);
|
||||
if (emailResult.rowExists()) {
|
||||
final String email = emailResult.getValue();
|
||||
if (!Utils.isEmailEmpty(email)) {
|
||||
String code = RandomStringUtils.generateNum(6); // 6 digits code
|
||||
@ -162,7 +162,7 @@ public class VerificationCodeManager implements SettingsDependent, HasCleanup {
|
||||
*
|
||||
* @param name the name of the player to generate a code for
|
||||
*/
|
||||
public void verify(String name){
|
||||
public void verify(String name) {
|
||||
verifiedPlayers.add(name.toLowerCase());
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ public class PlayerAuth {
|
||||
/** The player's name in the correct casing, e.g. "Xephi". */
|
||||
private String realName;
|
||||
private HashedPassword password;
|
||||
private String totpKey;
|
||||
private String email;
|
||||
private String lastIp;
|
||||
private int groupId;
|
||||
@ -160,6 +161,14 @@ public class PlayerAuth {
|
||||
this.registrationDate = registrationDate;
|
||||
}
|
||||
|
||||
public String getTotpKey() {
|
||||
return totpKey;
|
||||
}
|
||||
|
||||
public void setTotpKey(String totpKey) {
|
||||
this.totpKey = totpKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof PlayerAuth)) {
|
||||
@ -195,6 +204,7 @@ public class PlayerAuth {
|
||||
private String name;
|
||||
private String realName;
|
||||
private HashedPassword password;
|
||||
private String totpKey;
|
||||
private String lastIp;
|
||||
private String email;
|
||||
private int groupId = -1;
|
||||
@ -219,6 +229,7 @@ public class PlayerAuth {
|
||||
auth.nickname = checkNotNull(name).toLowerCase();
|
||||
auth.realName = firstNonNull(realName, "Player");
|
||||
auth.password = firstNonNull(password, new HashedPassword(""));
|
||||
auth.totpKey = totpKey;
|
||||
auth.email = DB_EMAIL_DEFAULT.equals(email) ? null : email;
|
||||
auth.lastIp = lastIp; // Don't check against default value 127.0.0.1 as it may be a legit value
|
||||
auth.groupId = groupId;
|
||||
@ -258,6 +269,11 @@ public class PlayerAuth {
|
||||
return password(new HashedPassword(hash, salt));
|
||||
}
|
||||
|
||||
public Builder totpKey(String totpKey) {
|
||||
this.totpKey = totpKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder lastIp(String lastIp) {
|
||||
this.lastIp = lastIp;
|
||||
return this;
|
||||
|
@ -32,7 +32,7 @@ public enum AllowFlightRestoreType {
|
||||
}
|
||||
},
|
||||
|
||||
/** Always set flight enabled to false. */
|
||||
/** The user's flight handling is not modified. */
|
||||
NOTHING {
|
||||
@Override
|
||||
public void restoreAllowFlight(Player player, LimboPlayer limbo) {
|
||||
|
@ -0,0 +1,11 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
public enum LimboMessageType {
|
||||
|
||||
REGISTER,
|
||||
|
||||
LOG_IN,
|
||||
|
||||
TOTP_CODE
|
||||
|
||||
}
|
@ -23,6 +23,7 @@ public class LimboPlayer {
|
||||
private final float flySpeed;
|
||||
private BukkitTask timeoutTask = null;
|
||||
private MessageTask messageTask = null;
|
||||
private LimboPlayerState state = LimboPlayerState.PASSWORD_REQUIRED;
|
||||
|
||||
public LimboPlayer(Location loc, boolean operator, Collection<String> groups, boolean fly, float walkSpeed,
|
||||
float flySpeed) {
|
||||
@ -124,4 +125,12 @@ public class LimboPlayer {
|
||||
setMessageTask(null);
|
||||
setTimeoutTask(null);
|
||||
}
|
||||
|
||||
public LimboPlayerState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(LimboPlayerState state) {
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
package fr.xephi.authme.data.limbo;
|
||||
|
||||
public enum LimboPlayerState {
|
||||
|
||||
PASSWORD_REQUIRED,
|
||||
|
||||
TOTP_REQUIRED
|
||||
|
||||
}
|
@ -45,11 +45,11 @@ class LimboPlayerTaskManager {
|
||||
*
|
||||
* @param player the player
|
||||
* @param limbo the associated limbo player of the player
|
||||
* @param isRegistered whether the player is registered or not (needed to determine the message in the task)
|
||||
* @param messageType message type
|
||||
*/
|
||||
void registerMessageTask(Player player, LimboPlayer limbo, boolean isRegistered) {
|
||||
void registerMessageTask(Player player, LimboPlayer limbo, LimboMessageType messageType) {
|
||||
int interval = settings.getProperty(RegistrationSettings.MESSAGE_INTERVAL);
|
||||
MessageResult result = getMessageKey(player.getName(), isRegistered);
|
||||
MessageResult result = getMessageKey(player.getName(), messageType);
|
||||
if (interval > 0) {
|
||||
String[] joinMessage = messages.retrieveSingle(player, result.messageKey, result.args).split("\n");
|
||||
MessageTask messageTask = new MessageTask(player, joinMessage);
|
||||
@ -89,12 +89,14 @@ class LimboPlayerTaskManager {
|
||||
* Returns the appropriate message key according to the registration status and settings.
|
||||
*
|
||||
* @param name the player's name
|
||||
* @param isRegistered whether or not the username is registered
|
||||
* @param messageType the message to show
|
||||
* @return the message key to display to the user
|
||||
*/
|
||||
private MessageResult getMessageKey(String name, boolean isRegistered) {
|
||||
if (isRegistered) {
|
||||
private MessageResult getMessageKey(String name, LimboMessageType messageType) {
|
||||
if (messageType == LimboMessageType.LOG_IN) {
|
||||
return new MessageResult(MessageKey.LOGIN_MESSAGE);
|
||||
} else if (messageType == LimboMessageType.TOTP_CODE) {
|
||||
return new MessageResult(MessageKey.TWO_FACTOR_CODE_REQUIRED);
|
||||
} else if (registrationCaptchaManager.isCaptchaRequired(name)) {
|
||||
final String captchaCode = registrationCaptchaManager.getCaptchaCodeOrGenerateNew(name);
|
||||
return new MessageResult(MessageKey.CAPTCHA_FOR_REGISTRATION_REQUIRED, captchaCode);
|
||||
|
@ -69,7 +69,8 @@ public class LimboService {
|
||||
LimboPlayer limboPlayer = helper.merge(existingLimbo, limboFromDisk);
|
||||
limboPlayer = helper.merge(helper.createLimboPlayer(player, isRegistered, location), limboPlayer);
|
||||
|
||||
taskManager.registerMessageTask(player, limboPlayer, isRegistered);
|
||||
taskManager.registerMessageTask(player, limboPlayer,
|
||||
isRegistered ? LimboMessageType.LOG_IN : LimboMessageType.REGISTER);
|
||||
taskManager.registerTimeoutTask(player, limboPlayer);
|
||||
helper.revokeLimboStates(player);
|
||||
authGroupHandler.setGroup(player, limboPlayer,
|
||||
@ -134,7 +135,7 @@ public class LimboService {
|
||||
Optional<LimboPlayer> limboPlayer = getLimboOrLogError(player, "reset tasks");
|
||||
limboPlayer.ifPresent(limbo -> {
|
||||
taskManager.registerTimeoutTask(player, limbo);
|
||||
taskManager.registerMessageTask(player, limbo, true);
|
||||
taskManager.registerMessageTask(player, limbo, LimboMessageType.LOG_IN);
|
||||
});
|
||||
authGroupHandler.setGroup(player, limboPlayer.orElse(null), AuthGroupType.REGISTERED_UNAUTHENTICATED);
|
||||
}
|
||||
@ -143,11 +144,11 @@ public class LimboService {
|
||||
* Resets the message task associated with the player's LimboPlayer.
|
||||
*
|
||||
* @param player the player to set a new message task for
|
||||
* @param isRegistered whether or not the player is registered
|
||||
* @param messageType the message to show for the limbo player
|
||||
*/
|
||||
public void resetMessageTask(Player player, boolean isRegistered) {
|
||||
public void resetMessageTask(Player player, LimboMessageType messageType) {
|
||||
getLimboOrLogError(player, "reset message task")
|
||||
.ifPresent(limbo -> taskManager.registerMessageTask(player, limbo, isRegistered));
|
||||
.ifPresent(limbo -> taskManager.registerMessageTask(player, limbo, messageType));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,7 +3,6 @@ package fr.xephi.authme.data.limbo;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.permission.PermissionsManager;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.SpawnLoader;
|
||||
import fr.xephi.authme.settings.properties.LimboSettings;
|
||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||
import org.bukkit.Location;
|
||||
@ -20,9 +19,6 @@ import static fr.xephi.authme.util.Utils.isCollectionEmpty;
|
||||
*/
|
||||
class LimboServiceHelper {
|
||||
|
||||
@Inject
|
||||
private SpawnLoader spawnLoader;
|
||||
|
||||
@Inject
|
||||
private PermissionsManager permissionsManager;
|
||||
|
||||
|
@ -0,0 +1,169 @@
|
||||
package fr.xephi.authme.datasource;
|
||||
|
||||
import ch.jalu.datasourcecolumns.data.DataSourceValue;
|
||||
import ch.jalu.datasourcecolumns.data.DataSourceValueImpl;
|
||||
import ch.jalu.datasourcecolumns.data.DataSourceValues;
|
||||
import ch.jalu.datasourcecolumns.predicate.AlwaysTruePredicate;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.datasource.columnshandler.AuthMeColumns;
|
||||
import fr.xephi.authme.datasource.columnshandler.AuthMeColumnsHandler;
|
||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static ch.jalu.datasourcecolumns.data.UpdateValues.with;
|
||||
import static ch.jalu.datasourcecolumns.predicate.StandardPredicates.eq;
|
||||
import static ch.jalu.datasourcecolumns.predicate.StandardPredicates.eqIgnoreCase;
|
||||
import static fr.xephi.authme.datasource.SqlDataSourceUtils.logSqlException;
|
||||
|
||||
/**
|
||||
* Common type for SQL-based data sources. Classes implementing this
|
||||
* must ensure that {@link #columnsHandler} is initialized on creation.
|
||||
*/
|
||||
public abstract class AbstractSqlDataSource implements DataSource {
|
||||
|
||||
protected AuthMeColumnsHandler columnsHandler;
|
||||
|
||||
@Override
|
||||
public boolean isAuthAvailable(String user) {
|
||||
try {
|
||||
return columnsHandler.retrieve(user, AuthMeColumns.NAME).rowExists();
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashedPassword getPassword(String user) {
|
||||
try {
|
||||
DataSourceValues values = columnsHandler.retrieve(user, AuthMeColumns.PASSWORD, AuthMeColumns.SALT);
|
||||
if (values.rowExists()) {
|
||||
return new HashedPassword(values.get(AuthMeColumns.PASSWORD), values.get(AuthMeColumns.SALT));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveAuth(PlayerAuth auth) {
|
||||
return columnsHandler.insert(auth,
|
||||
AuthMeColumns.NAME, AuthMeColumns.NICK_NAME, AuthMeColumns.PASSWORD, AuthMeColumns.SALT,
|
||||
AuthMeColumns.EMAIL, AuthMeColumns.REGISTRATION_DATE, AuthMeColumns.REGISTRATION_IP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSession(String user) {
|
||||
try {
|
||||
DataSourceValue<Integer> result = columnsHandler.retrieve(user, AuthMeColumns.HAS_SESSION);
|
||||
return result.rowExists() && Integer.valueOf(1).equals(result.getValue());
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSession(PlayerAuth auth) {
|
||||
return columnsHandler.update(auth, AuthMeColumns.LAST_IP, AuthMeColumns.LAST_LOGIN, AuthMeColumns.NICK_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updatePassword(PlayerAuth auth) {
|
||||
return updatePassword(auth.getNickname(), auth.getPassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updatePassword(String user, HashedPassword password) {
|
||||
return columnsHandler.update(user,
|
||||
with(AuthMeColumns.PASSWORD, password.getHash())
|
||||
.and(AuthMeColumns.SALT, password.getSalt()).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateQuitLoc(PlayerAuth auth) {
|
||||
return columnsHandler.update(auth,
|
||||
AuthMeColumns.LOCATION_X, AuthMeColumns.LOCATION_Y, AuthMeColumns.LOCATION_Z,
|
||||
AuthMeColumns.LOCATION_WORLD, AuthMeColumns.LOCATION_YAW, AuthMeColumns.LOCATION_PITCH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAllAuthsByIp(String ip) {
|
||||
try {
|
||||
return columnsHandler.retrieve(eq(AuthMeColumns.LAST_IP, ip), AuthMeColumns.NAME);
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countAuthsByEmail(String email) {
|
||||
return columnsHandler.count(eqIgnoreCase(AuthMeColumns.EMAIL, email));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateEmail(PlayerAuth auth) {
|
||||
return columnsHandler.update(auth, AuthMeColumns.EMAIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogged(String user) {
|
||||
try {
|
||||
DataSourceValue<Integer> result = columnsHandler.retrieve(user, AuthMeColumns.IS_LOGGED);
|
||||
return result.rowExists() && Integer.valueOf(1).equals(result.getValue());
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogged(String user) {
|
||||
columnsHandler.update(user, AuthMeColumns.IS_LOGGED, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnlogged(String user) {
|
||||
columnsHandler.update(user, AuthMeColumns.IS_LOGGED, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantSession(String user) {
|
||||
columnsHandler.update(user, AuthMeColumns.HAS_SESSION, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revokeSession(String user) {
|
||||
columnsHandler.update(user, AuthMeColumns.HAS_SESSION, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeLogged() {
|
||||
columnsHandler.update(eq(AuthMeColumns.IS_LOGGED, 1), AuthMeColumns.IS_LOGGED, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAccountsRegistered() {
|
||||
return columnsHandler.count(new AlwaysTruePredicate<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateRealName(String user, String realName) {
|
||||
return columnsHandler.update(user, AuthMeColumns.NICK_NAME, realName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceValue<String> getEmail(String user) {
|
||||
try {
|
||||
return columnsHandler.retrieve(user, AuthMeColumns.EMAIL);
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
return DataSourceValueImpl.unknownRow();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package fr.xephi.authme.datasource;
|
||||
|
||||
import ch.jalu.datasourcecolumns.data.DataSourceValue;
|
||||
import ch.jalu.datasourcecolumns.data.DataSourceValueImpl;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
@ -244,10 +246,10 @@ public class CacheDataSource implements DataSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceResult<String> getEmail(String user) {
|
||||
public DataSourceValue<String> getEmail(String user) {
|
||||
return cachedAuths.getUnchecked(user)
|
||||
.map(auth -> DataSourceResult.of(auth.getEmail()))
|
||||
.orElse(DataSourceResult.unknownPlayer());
|
||||
.map(auth -> DataSourceValueImpl.of(auth.getEmail()))
|
||||
.orElse(DataSourceValueImpl.unknownRow());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -268,6 +270,15 @@ public class CacheDataSource implements DataSource {
|
||||
return source.getRecentlyLoggedInPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setTotpKey(String user, String totpKey) {
|
||||
boolean result = source.setTotpKey(user, totpKey);
|
||||
if (result) {
|
||||
cachedAuths.refresh(user);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCache(String playerName) {
|
||||
cachedAuths.invalidate(playerName);
|
||||
|
@ -14,6 +14,7 @@ public final class Columns {
|
||||
public final String REAL_NAME;
|
||||
public final String PASSWORD;
|
||||
public final String SALT;
|
||||
public final String TOTP_KEY;
|
||||
public final String LAST_IP;
|
||||
public final String LAST_LOGIN;
|
||||
public final String GROUP;
|
||||
@ -35,6 +36,7 @@ public final class Columns {
|
||||
REAL_NAME = settings.getProperty(DatabaseSettings.MYSQL_COL_REALNAME);
|
||||
PASSWORD = settings.getProperty(DatabaseSettings.MYSQL_COL_PASSWORD);
|
||||
SALT = settings.getProperty(DatabaseSettings.MYSQL_COL_SALT);
|
||||
TOTP_KEY = settings.getProperty(DatabaseSettings.MYSQL_COL_TOTP_KEY);
|
||||
LAST_IP = settings.getProperty(DatabaseSettings.MYSQL_COL_LAST_IP);
|
||||
LAST_LOGIN = settings.getProperty(DatabaseSettings.MYSQL_COL_LASTLOGIN);
|
||||
GROUP = settings.getProperty(DatabaseSettings.MYSQL_COL_GROUP);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package fr.xephi.authme.datasource;
|
||||
|
||||
import ch.jalu.datasourcecolumns.data.DataSourceValue;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.initialization.Reloadable;
|
||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||
@ -216,7 +217,7 @@ public interface DataSource extends Reloadable {
|
||||
* @param user the user to retrieve an email for
|
||||
* @return the email saved for the user, or null if user or email is not present
|
||||
*/
|
||||
DataSourceResult<String> getEmail(String user);
|
||||
DataSourceValue<String> getEmail(String user);
|
||||
|
||||
/**
|
||||
* Return all players of the database.
|
||||
@ -232,6 +233,25 @@ public interface DataSource extends Reloadable {
|
||||
*/
|
||||
List<PlayerAuth> getRecentlyLoggedInPlayers();
|
||||
|
||||
/**
|
||||
* Sets the given TOTP key to the player's account.
|
||||
*
|
||||
* @param user the name of the player to modify
|
||||
* @param totpKey the totp key to set
|
||||
* @return True upon success, false upon failure
|
||||
*/
|
||||
boolean setTotpKey(String user, String totpKey);
|
||||
|
||||
/**
|
||||
* Removes the TOTP key if present of the given player's account.
|
||||
*
|
||||
* @param user the name of the player to modify
|
||||
* @return True upon success, false upon failure
|
||||
*/
|
||||
default boolean removeTotpKey(String user) {
|
||||
return setTotpKey(user, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the data source.
|
||||
*/
|
||||
|
@ -1,53 +0,0 @@
|
||||
package fr.xephi.authme.datasource;
|
||||
|
||||
/**
|
||||
* Wraps a value and allows to specify whether a value is missing or the player is not registered.
|
||||
*/
|
||||
public final class DataSourceResult<T> {
|
||||
|
||||
/** Instance used when a player does not exist. */
|
||||
private static final DataSourceResult UNKNOWN_PLAYER = new DataSourceResult<>(null);
|
||||
private final T value;
|
||||
|
||||
private DataSourceResult(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link DataSourceResult} for the given value.
|
||||
*
|
||||
* @param value the value to wrap
|
||||
* @param <T> the value's type
|
||||
* @return DataSourceResult object for the given value
|
||||
*/
|
||||
public static <T> DataSourceResult<T> of(T value) {
|
||||
return new DataSourceResult<>(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link DataSourceResult} specifying that the player does not exist.
|
||||
*
|
||||
* @param <T> the value type
|
||||
* @return data source result for unknown player
|
||||
*/
|
||||
public static <T> DataSourceResult<T> unknownPlayer() {
|
||||
return UNKNOWN_PLAYER;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the player of the associated value exists
|
||||
*/
|
||||
public boolean playerExists() {
|
||||
return this != UNKNOWN_PLAYER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value. It is {@code null} if the player is unknown. It is also {@code null}
|
||||
* if the player exists but does not have the value defined.
|
||||
*
|
||||
* @return the value, or null
|
||||
*/
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package fr.xephi.authme.datasource;
|
||||
|
||||
import ch.jalu.datasourcecolumns.data.DataSourceValue;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||
@ -366,7 +367,7 @@ public class FlatFile implements DataSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceResult<String> getEmail(String user) {
|
||||
public DataSourceValue<String> getEmail(String user) {
|
||||
throw new UnsupportedOperationException("Flat file no longer supported");
|
||||
}
|
||||
|
||||
@ -398,6 +399,11 @@ public class FlatFile implements DataSource {
|
||||
throw new UnsupportedOperationException("Flat file no longer supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setTotpKey(String user, String totpKey) {
|
||||
throw new UnsupportedOperationException("Flat file no longer supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PlayerAuth object from the read data.
|
||||
*
|
||||
|
@ -5,13 +5,12 @@ import com.zaxxer.hikari.HikariDataSource;
|
||||
import com.zaxxer.hikari.pool.HikariPool.PoolInitializationException;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.datasource.columnshandler.AuthMeColumnsHandler;
|
||||
import fr.xephi.authme.datasource.mysqlextensions.MySqlExtension;
|
||||
import fr.xephi.authme.datasource.mysqlextensions.MySqlExtensionsFactory;
|
||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.DatabaseSettings;
|
||||
import fr.xephi.authme.settings.properties.HooksSettings;
|
||||
import fr.xephi.authme.util.StringUtils;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
@ -32,7 +31,7 @@ import static fr.xephi.authme.datasource.SqlDataSourceUtils.logSqlException;
|
||||
* MySQL data source.
|
||||
*/
|
||||
@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) // Justification: Class name cannot be changed anymore
|
||||
public class MySQL implements DataSource {
|
||||
public class MySQL extends AbstractSqlDataSource {
|
||||
|
||||
private boolean useSsl;
|
||||
private String host;
|
||||
@ -99,6 +98,7 @@ public class MySQL implements DataSource {
|
||||
this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
|
||||
this.columnOthers = settings.getProperty(HooksSettings.MYSQL_OTHER_USERNAME_COLS);
|
||||
this.col = new Columns(settings);
|
||||
this.columnsHandler = AuthMeColumnsHandler.createForMySql(this::getConnection, settings);
|
||||
this.sqlExtension = extensionsFactory.buildExtension(col);
|
||||
this.poolSize = settings.getProperty(DatabaseSettings.MYSQL_POOL_SIZE);
|
||||
this.maxLifetime = settings.getProperty(DatabaseSettings.MYSQL_CONNECTION_MAX_LIFETIME);
|
||||
@ -252,6 +252,11 @@ public class MySQL implements DataSource {
|
||||
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN "
|
||||
+ col.HAS_SESSION + " SMALLINT NOT NULL DEFAULT '0' AFTER " + col.IS_LOGGED);
|
||||
}
|
||||
|
||||
if (isColumnMissing(md, col.TOTP_KEY)) {
|
||||
st.executeUpdate("ALTER TABLE " + tableName
|
||||
+ " ADD COLUMN " + col.TOTP_KEY + " VARCHAR(16);");
|
||||
}
|
||||
}
|
||||
ConsoleLogger.info("MySQL setup finished");
|
||||
}
|
||||
@ -262,40 +267,6 @@ public class MySQL implements DataSource {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuthAvailable(String user) {
|
||||
String sql = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user.toLowerCase());
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
return rs.next();
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashedPassword getPassword(String user) {
|
||||
boolean useSalt = !col.SALT.isEmpty();
|
||||
String sql = "SELECT " + col.PASSWORD
|
||||
+ (useSalt ? ", " + col.SALT : "")
|
||||
+ " FROM " + tableName + " WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user.toLowerCase());
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return new HashedPassword(rs.getString(col.PASSWORD),
|
||||
useSalt ? rs.getString(col.SALT) : null);
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerAuth getAuth(String user) {
|
||||
String sql = "SELECT * FROM " + tableName + " WHERE " + col.NAME + "=?;";
|
||||
@ -318,33 +289,9 @@ public class MySQL implements DataSource {
|
||||
|
||||
@Override
|
||||
public boolean saveAuth(PlayerAuth auth) {
|
||||
super.saveAuth(auth);
|
||||
|
||||
try (Connection con = getConnection()) {
|
||||
// TODO ljacqu 20171104: Replace with generic columns util to clean this up
|
||||
boolean useSalt = !col.SALT.isEmpty() || !StringUtils.isEmpty(auth.getPassword().getSalt());
|
||||
boolean hasEmail = auth.getEmail() != null;
|
||||
String emailPlaceholder = hasEmail ? "?" : "DEFAULT";
|
||||
|
||||
String sql = "INSERT INTO " + tableName + "("
|
||||
+ col.NAME + "," + col.PASSWORD + "," + col.REAL_NAME
|
||||
+ "," + col.EMAIL + "," + col.REGISTRATION_DATE + "," + col.REGISTRATION_IP
|
||||
+ (useSalt ? "," + col.SALT : "")
|
||||
+ ") VALUES (?,?,?," + emailPlaceholder + ",?,?" + (useSalt ? ",?" : "") + ");";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
int index = 1;
|
||||
pst.setString(index++, auth.getNickname());
|
||||
pst.setString(index++, auth.getPassword().getHash());
|
||||
pst.setString(index++, auth.getRealName());
|
||||
if (hasEmail) {
|
||||
pst.setString(index++, auth.getEmail());
|
||||
}
|
||||
pst.setObject(index++, auth.getRegistrationDate());
|
||||
pst.setString(index++, auth.getRegistrationIp());
|
||||
if (useSalt) {
|
||||
pst.setString(index++, auth.getPassword().getSalt());
|
||||
}
|
||||
pst.executeUpdate();
|
||||
}
|
||||
|
||||
if (!columnOthers.isEmpty()) {
|
||||
for (String column : columnOthers) {
|
||||
try (PreparedStatement pst = con.prepareStatement(
|
||||
@ -364,59 +311,6 @@ public class MySQL implements DataSource {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updatePassword(PlayerAuth auth) {
|
||||
return updatePassword(auth.getNickname(), auth.getPassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updatePassword(String user, HashedPassword password) {
|
||||
user = user.toLowerCase();
|
||||
try (Connection con = getConnection()) {
|
||||
boolean useSalt = !col.SALT.isEmpty();
|
||||
if (useSalt) {
|
||||
String sql = String.format("UPDATE %s SET %s = ?, %s = ? WHERE %s = ?;",
|
||||
tableName, col.PASSWORD, col.SALT, col.NAME);
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, password.getHash());
|
||||
pst.setString(2, password.getSalt());
|
||||
pst.setString(3, user);
|
||||
pst.executeUpdate();
|
||||
}
|
||||
} else {
|
||||
String sql = String.format("UPDATE %s SET %s = ? WHERE %s = ?;",
|
||||
tableName, col.PASSWORD, col.NAME);
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, password.getHash());
|
||||
pst.setString(2, user);
|
||||
pst.executeUpdate();
|
||||
}
|
||||
}
|
||||
sqlExtension.changePassword(user, password, con);
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSession(PlayerAuth auth) {
|
||||
String sql = "UPDATE " + tableName + " SET "
|
||||
+ col.LAST_IP + "=?, " + col.LAST_LOGIN + "=?, " + col.REAL_NAME + "=? WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, auth.getLastIp());
|
||||
pst.setObject(2, auth.getLastLogin());
|
||||
pst.setString(3, auth.getRealName());
|
||||
pst.setString(4, auth.getNickname());
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRecordsToPurge(long until) {
|
||||
Set<String> list = new HashSet<>();
|
||||
@ -454,42 +348,6 @@ public class MySQL implements DataSource {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateQuitLoc(PlayerAuth auth) {
|
||||
String sql = "UPDATE " + tableName
|
||||
+ " SET " + col.LASTLOC_X + " =?, " + col.LASTLOC_Y + "=?, " + col.LASTLOC_Z + "=?, "
|
||||
+ col.LASTLOC_WORLD + "=?, " + col.LASTLOC_YAW + "=?, " + col.LASTLOC_PITCH + "=?"
|
||||
+ " WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setDouble(1, auth.getQuitLocX());
|
||||
pst.setDouble(2, auth.getQuitLocY());
|
||||
pst.setDouble(3, auth.getQuitLocZ());
|
||||
pst.setString(4, auth.getWorld());
|
||||
pst.setFloat(5, auth.getYaw());
|
||||
pst.setFloat(6, auth.getPitch());
|
||||
pst.setString(7, auth.getNickname());
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateEmail(PlayerAuth auth) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.EMAIL + " =? WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, auth.getEmail());
|
||||
pst.setString(2, auth.getNickname());
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeConnection() {
|
||||
if (ds != null && !ds.isClosed()) {
|
||||
@ -497,39 +355,6 @@ public class MySQL implements DataSource {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAllAuthsByIp(String ip) {
|
||||
List<String> result = new ArrayList<>();
|
||||
String sql = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.LAST_IP + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, ip);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
result.add(rs.getString(col.NAME));
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countAuthsByEmail(String email) {
|
||||
String sql = "SELECT COUNT(1) FROM " + tableName + " WHERE UPPER(" + col.EMAIL + ") = UPPER(?)";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, email);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return rs.getInt(1);
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeRecords(Collection<String> toPurge) {
|
||||
String sql = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;";
|
||||
@ -548,140 +373,6 @@ public class MySQL implements DataSource {
|
||||
return DataSourceType.MYSQL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogged(String user) {
|
||||
String sql = "SELECT " + col.IS_LOGGED + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
return rs.next() && (rs.getInt(col.IS_LOGGED) == 1);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogged(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 1);
|
||||
pst.setString(2, user.toLowerCase());
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnlogged(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 0);
|
||||
pst.setString(2, user.toLowerCase());
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSession(String user) {
|
||||
String sql = "SELECT " + col.HAS_SESSION + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user.toLowerCase());
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
return rs.next() && (rs.getInt(col.HAS_SESSION) == 1);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantSession(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.HAS_SESSION + "=? WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 1);
|
||||
pst.setString(2, user.toLowerCase());
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revokeSession(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.HAS_SESSION + "=? WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 0);
|
||||
pst.setString(2, user.toLowerCase());
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeLogged() {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE " + col.IS_LOGGED + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 0);
|
||||
pst.setInt(2, 1);
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAccountsRegistered() {
|
||||
int result = 0;
|
||||
String sql = "SELECT COUNT(*) FROM " + tableName;
|
||||
try (Connection con = getConnection();
|
||||
Statement st = con.createStatement();
|
||||
ResultSet rs = st.executeQuery(sql)) {
|
||||
if (rs.next()) {
|
||||
result = rs.getInt(1);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateRealName(String user, String realName) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.REAL_NAME + "=? WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, realName);
|
||||
pst.setString(2, user);
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceResult<String> getEmail(String user) {
|
||||
String sql = "SELECT " + col.EMAIL + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return DataSourceResult.of(rs.getString(1));
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return DataSourceResult.unknownPlayer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PlayerAuth> getAllAuths() {
|
||||
List<PlayerAuth> auths = new ArrayList<>();
|
||||
@ -732,6 +423,20 @@ public class MySQL implements DataSource {
|
||||
return players;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setTotpKey(String user, String totpKey) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.TOTP_KEY + " = ? WHERE " + col.NAME + " = ?";
|
||||
try (Connection con = getConnection(); PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, totpKey);
|
||||
pst.setString(2, user.toLowerCase());
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PlayerAuth} object with the data from the provided result set.
|
||||
*
|
||||
@ -746,6 +451,7 @@ public class MySQL implements DataSource {
|
||||
.name(row.getString(col.NAME))
|
||||
.realName(row.getString(col.REAL_NAME))
|
||||
.password(row.getString(col.PASSWORD), salt)
|
||||
.totpKey(row.getString(col.TOTP_KEY))
|
||||
.lastLogin(getNullableLong(row, col.LAST_LOGIN))
|
||||
.lastIp(row.getString(col.LAST_IP))
|
||||
.email(row.getString(col.EMAIL))
|
||||
|
@ -3,10 +3,9 @@ package fr.xephi.authme.datasource;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||
import fr.xephi.authme.datasource.columnshandler.AuthMeColumnsHandler;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.DatabaseSettings;
|
||||
import fr.xephi.authme.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
@ -29,7 +28,7 @@ import static fr.xephi.authme.datasource.SqlDataSourceUtils.logSqlException;
|
||||
* SQLite data source.
|
||||
*/
|
||||
@SuppressWarnings({"checkstyle:AbbreviationAsWordInName"}) // Justification: Class name cannot be changed anymore
|
||||
public class SQLite implements DataSource {
|
||||
public class SQLite extends AbstractSqlDataSource {
|
||||
|
||||
private final Settings settings;
|
||||
private final File dataFolder;
|
||||
@ -71,6 +70,7 @@ public class SQLite implements DataSource {
|
||||
this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
|
||||
this.col = new Columns(settings);
|
||||
this.con = connection;
|
||||
this.columnsHandler = AuthMeColumnsHandler.createForSqlite(con, settings);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,6 +85,7 @@ public class SQLite implements DataSource {
|
||||
|
||||
ConsoleLogger.debug("SQLite driver loaded");
|
||||
this.con = DriverManager.getConnection("jdbc:sqlite:plugins/AuthMe/" + database + ".db");
|
||||
this.columnsHandler = AuthMeColumnsHandler.createForSqlite(con, settings);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,6 +173,11 @@ public class SQLite implements DataSource {
|
||||
st.executeUpdate("ALTER TABLE " + tableName
|
||||
+ " ADD COLUMN " + col.HAS_SESSION + " INT NOT NULL DEFAULT '0';");
|
||||
}
|
||||
|
||||
if (isColumnMissing(md, col.TOTP_KEY)) {
|
||||
st.executeUpdate("ALTER TABLE " + tableName
|
||||
+ " ADD COLUMN " + col.TOTP_KEY + " VARCHAR(16);");
|
||||
}
|
||||
}
|
||||
ConsoleLogger.info("SQLite Setup finished");
|
||||
}
|
||||
@ -204,40 +210,6 @@ public class SQLite implements DataSource {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuthAvailable(String user) {
|
||||
String sql = "SELECT 1 FROM " + tableName + " WHERE LOWER(" + col.NAME + ")=LOWER(?);";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
return rs.next();
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
ConsoleLogger.warning(ex.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashedPassword getPassword(String user) {
|
||||
boolean useSalt = !col.SALT.isEmpty();
|
||||
String sql = "SELECT " + col.PASSWORD
|
||||
+ (useSalt ? ", " + col.SALT : "")
|
||||
+ " FROM " + tableName + " WHERE " + col.NAME + "=?";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return new HashedPassword(rs.getString(col.PASSWORD),
|
||||
useSalt ? rs.getString(col.SALT) : null);
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerAuth getAuth(String user) {
|
||||
String sql = "SELECT * FROM " + tableName + " WHERE LOWER(" + col.NAME + ")=LOWER(?);";
|
||||
@ -254,95 +226,6 @@ public class SQLite implements DataSource {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveAuth(PlayerAuth auth) {
|
||||
PreparedStatement pst = null;
|
||||
try {
|
||||
HashedPassword password = auth.getPassword();
|
||||
if (col.SALT.isEmpty()) {
|
||||
if (!StringUtils.isEmpty(auth.getPassword().getSalt())) {
|
||||
ConsoleLogger.warning("Warning! Detected hashed password with separate salt but the salt column "
|
||||
+ "is not set in the config!");
|
||||
}
|
||||
|
||||
pst = con.prepareStatement("INSERT INTO " + tableName + "(" + col.NAME + "," + col.PASSWORD
|
||||
+ "," + col.REAL_NAME + "," + col.EMAIL
|
||||
+ "," + col.REGISTRATION_DATE + "," + col.REGISTRATION_IP
|
||||
+ ") VALUES (?,?,?,?,?,?);");
|
||||
pst.setString(1, auth.getNickname());
|
||||
pst.setString(2, password.getHash());
|
||||
pst.setString(3, auth.getRealName());
|
||||
pst.setString(4, auth.getEmail());
|
||||
pst.setLong(5, auth.getRegistrationDate());
|
||||
pst.setString(6, auth.getRegistrationIp());
|
||||
pst.executeUpdate();
|
||||
} else {
|
||||
pst = con.prepareStatement("INSERT INTO " + tableName + "(" + col.NAME + "," + col.PASSWORD
|
||||
+ "," + col.REAL_NAME + "," + col.EMAIL
|
||||
+ "," + col.REGISTRATION_DATE + "," + col.REGISTRATION_IP + "," + col.SALT
|
||||
+ ") VALUES (?,?,?,?,?,?,?);");
|
||||
pst.setString(1, auth.getNickname());
|
||||
pst.setString(2, password.getHash());
|
||||
pst.setString(3, auth.getRealName());
|
||||
pst.setString(4, auth.getEmail());
|
||||
pst.setLong(5, auth.getRegistrationDate());
|
||||
pst.setString(6, auth.getRegistrationIp());
|
||||
pst.setString(7, password.getSalt());
|
||||
pst.executeUpdate();
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
} finally {
|
||||
close(pst);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updatePassword(PlayerAuth auth) {
|
||||
return updatePassword(auth.getNickname(), auth.getPassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updatePassword(String user, HashedPassword password) {
|
||||
user = user.toLowerCase();
|
||||
boolean useSalt = !col.SALT.isEmpty();
|
||||
String sql = "UPDATE " + tableName + " SET " + col.PASSWORD + " = ?"
|
||||
+ (useSalt ? ", " + col.SALT + " = ?" : "")
|
||||
+ " WHERE " + col.NAME + " = ?";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)){
|
||||
pst.setString(1, password.getHash());
|
||||
if (useSalt) {
|
||||
pst.setString(2, password.getSalt());
|
||||
pst.setString(3, user);
|
||||
} else {
|
||||
pst.setString(2, user);
|
||||
}
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSession(PlayerAuth auth) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.LAST_IP + "=?, " + col.LAST_LOGIN + "=?, "
|
||||
+ col.REAL_NAME + "=? WHERE " + col.NAME + "=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)){
|
||||
pst.setString(1, auth.getLastIp());
|
||||
pst.setObject(2, auth.getLastLogin());
|
||||
pst.setString(3, auth.getRealName());
|
||||
pst.setString(4, auth.getNickname());
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRecordsToPurge(long until) {
|
||||
Set<String> list = new HashSet<>();
|
||||
@ -390,42 +273,6 @@ public class SQLite implements DataSource {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateQuitLoc(PlayerAuth auth) {
|
||||
String sql = "UPDATE " + tableName + " SET "
|
||||
+ col.LASTLOC_X + "=?, " + col.LASTLOC_Y + "=?, " + col.LASTLOC_Z + "=?, "
|
||||
+ col.LASTLOC_WORLD + "=?, " + col.LASTLOC_YAW + "=?, " + col.LASTLOC_PITCH + "=? "
|
||||
+ "WHERE " + col.NAME + "=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setDouble(1, auth.getQuitLocX());
|
||||
pst.setDouble(2, auth.getQuitLocY());
|
||||
pst.setDouble(3, auth.getQuitLocZ());
|
||||
pst.setString(4, auth.getWorld());
|
||||
pst.setFloat(5, auth.getYaw());
|
||||
pst.setFloat(6, auth.getPitch());
|
||||
pst.setString(7, auth.getNickname());
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateEmail(PlayerAuth auth) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.EMAIL + "=? WHERE " + col.NAME + "=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, auth.getEmail());
|
||||
pst.setString(2, auth.getNickname());
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeConnection() {
|
||||
try {
|
||||
@ -437,180 +284,11 @@ public class SQLite implements DataSource {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAllAuthsByIp(String ip) {
|
||||
List<String> countIp = new ArrayList<>();
|
||||
String sql = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.LAST_IP + "=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, ip);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
countIp.add(rs.getString(col.NAME));
|
||||
}
|
||||
return countIp;
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countAuthsByEmail(String email) {
|
||||
String sql = "SELECT COUNT(1) FROM " + tableName + " WHERE " + col.EMAIL + " = ? COLLATE NOCASE;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, email);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return rs.getInt(1);
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceType getType() {
|
||||
return DataSourceType.SQLITE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogged(String user) {
|
||||
String sql = "SELECT " + col.IS_LOGGED + " FROM " + tableName + " WHERE LOWER(" + col.NAME + ")=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return rs.getInt(col.IS_LOGGED) == 1;
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogged(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE LOWER(" + col.NAME + ")=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 1);
|
||||
pst.setString(2, user);
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnlogged(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE LOWER(" + col.NAME + ")=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 0);
|
||||
pst.setString(2, user);
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSession(String user) {
|
||||
String sql = "SELECT " + col.HAS_SESSION + " FROM " + tableName + " WHERE LOWER(" + col.NAME + ")=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user.toLowerCase());
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return rs.getInt(col.HAS_SESSION) == 1;
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantSession(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.HAS_SESSION + "=? WHERE LOWER(" + col.NAME + ")=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 1);
|
||||
pst.setString(2, user.toLowerCase());
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revokeSession(String user) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.HAS_SESSION + "=? WHERE LOWER(" + col.NAME + ")=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 0);
|
||||
pst.setString(2, user.toLowerCase());
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeLogged() {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE " + col.IS_LOGGED + "=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setInt(1, 0);
|
||||
pst.setInt(2, 1);
|
||||
pst.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAccountsRegistered() {
|
||||
String sql = "SELECT COUNT(*) FROM " + tableName + ";";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql); ResultSet rs = pst.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return rs.getInt(1);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateRealName(String user, String realName) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.REAL_NAME + "=? WHERE " + col.NAME + "=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, realName);
|
||||
pst.setString(2, user);
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceResult<String> getEmail(String user) {
|
||||
String sql = "SELECT " + col.EMAIL + " FROM " + tableName + " WHERE " + col.NAME + "=?;";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, user);
|
||||
try (ResultSet rs = pst.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return DataSourceResult.of(rs.getString(1));
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
return DataSourceResult.unknownPlayer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PlayerAuth> getAllAuths() {
|
||||
List<PlayerAuth> auths = new ArrayList<>();
|
||||
@ -655,6 +333,21 @@ public class SQLite implements DataSource {
|
||||
return players;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean setTotpKey(String user, String totpKey) {
|
||||
String sql = "UPDATE " + tableName + " SET " + col.TOTP_KEY + " = ? WHERE " + col.NAME + " = ?";
|
||||
try (PreparedStatement pst = con.prepareStatement(sql)) {
|
||||
pst.setString(1, totpKey);
|
||||
pst.setString(2, user.toLowerCase());
|
||||
pst.executeUpdate();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
|
||||
String salt = !col.SALT.isEmpty() ? row.getString(col.SALT) : null;
|
||||
|
||||
@ -663,6 +356,7 @@ public class SQLite implements DataSource {
|
||||
.email(row.getString(col.EMAIL))
|
||||
.realName(row.getString(col.REAL_NAME))
|
||||
.password(row.getString(col.PASSWORD), salt)
|
||||
.totpKey(row.getString(col.TOTP_KEY))
|
||||
.lastLogin(getNullableLong(row, col.LAST_LOGIN))
|
||||
.lastIp(row.getString(col.LAST_IP))
|
||||
.registrationDate(row.getLong(col.REGISTRATION_DATE))
|
||||
@ -695,16 +389,6 @@ public class SQLite implements DataSource {
|
||||
+ currentTimestamp + ", to all " + updatedRows + " rows");
|
||||
}
|
||||
|
||||
private static void close(Statement st) {
|
||||
if (st != null) {
|
||||
try {
|
||||
st.close();
|
||||
} catch (SQLException ex) {
|
||||
logSqlException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void close(Connection con) {
|
||||
if (con != null) {
|
||||
try {
|
||||
|
@ -0,0 +1,82 @@
|
||||
package fr.xephi.authme.datasource.columnshandler;
|
||||
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.settings.properties.DatabaseSettings;
|
||||
|
||||
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.ColumnOptions.DEFAULT_FOR_NULL;
|
||||
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.ColumnOptions.OPTIONAL;
|
||||
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.createDouble;
|
||||
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.createFloat;
|
||||
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.createInteger;
|
||||
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.createLong;
|
||||
import static fr.xephi.authme.datasource.columnshandler.AuthMeColumnsFactory.createString;
|
||||
|
||||
/**
|
||||
* Contains column definitions for the AuthMe table.
|
||||
*/
|
||||
public final class AuthMeColumns {
|
||||
|
||||
public static final PlayerAuthColumn<String> NAME = createString(
|
||||
DatabaseSettings.MYSQL_COL_NAME, PlayerAuth::getNickname);
|
||||
|
||||
public static final PlayerAuthColumn<String> NICK_NAME = createString(
|
||||
DatabaseSettings.MYSQL_COL_REALNAME, PlayerAuth::getRealName);
|
||||
|
||||
public static final PlayerAuthColumn<String> PASSWORD = createString(
|
||||
DatabaseSettings.MYSQL_COL_PASSWORD, auth -> auth.getPassword().getHash());
|
||||
|
||||
public static final PlayerAuthColumn<String> SALT = createString(
|
||||
DatabaseSettings.MYSQL_COL_SALT, auth -> auth.getPassword().getSalt(), OPTIONAL);
|
||||
|
||||
public static final PlayerAuthColumn<String> EMAIL = createString(
|
||||
DatabaseSettings.MYSQL_COL_EMAIL, PlayerAuth::getEmail, DEFAULT_FOR_NULL);
|
||||
|
||||
public static final PlayerAuthColumn<String> LAST_IP = createString(
|
||||
DatabaseSettings.MYSQL_COL_LAST_IP, PlayerAuth::getLastIp);
|
||||
|
||||
public static final PlayerAuthColumn<Integer> GROUP_ID = createInteger(
|
||||
DatabaseSettings.MYSQL_COL_GROUP, PlayerAuth::getGroupId, OPTIONAL);
|
||||
|
||||
public static final PlayerAuthColumn<Long> LAST_LOGIN = createLong(
|
||||
DatabaseSettings.MYSQL_COL_LASTLOGIN, PlayerAuth::getLastLogin);
|
||||
|
||||
public static final PlayerAuthColumn<String> REGISTRATION_IP = createString(
|
||||
DatabaseSettings.MYSQL_COL_REGISTER_IP, PlayerAuth::getRegistrationIp);
|
||||
|
||||
public static final PlayerAuthColumn<Long> REGISTRATION_DATE = createLong(
|
||||
DatabaseSettings.MYSQL_COL_REGISTER_DATE, PlayerAuth::getRegistrationDate);
|
||||
|
||||
// --------
|
||||
// Location columns
|
||||
// --------
|
||||
public static final PlayerAuthColumn<Double> LOCATION_X = createDouble(
|
||||
DatabaseSettings.MYSQL_COL_LASTLOC_X, PlayerAuth::getQuitLocX);
|
||||
|
||||
public static final PlayerAuthColumn<Double> LOCATION_Y = createDouble(
|
||||
DatabaseSettings.MYSQL_COL_LASTLOC_Y, PlayerAuth::getQuitLocY);
|
||||
|
||||
public static final PlayerAuthColumn<Double> LOCATION_Z = createDouble(
|
||||
DatabaseSettings.MYSQL_COL_LASTLOC_Z, PlayerAuth::getQuitLocZ);
|
||||
|
||||
public static final PlayerAuthColumn<String> LOCATION_WORLD = createString(
|
||||
DatabaseSettings.MYSQL_COL_LASTLOC_WORLD, PlayerAuth::getWorld);
|
||||
|
||||
public static final PlayerAuthColumn<Float> LOCATION_YAW = createFloat(
|
||||
DatabaseSettings.MYSQL_COL_LASTLOC_YAW, PlayerAuth::getYaw);
|
||||
|
||||
public static final PlayerAuthColumn<Float> LOCATION_PITCH = createFloat(
|
||||
DatabaseSettings.MYSQL_COL_LASTLOC_PITCH, PlayerAuth::getPitch);
|
||||
|
||||
// --------
|
||||
// Columns not on PlayerAuth
|
||||
// --------
|
||||
public static final DataSourceColumn<Integer> IS_LOGGED = createInteger(
|
||||
DatabaseSettings.MYSQL_COL_ISLOGGED);
|
||||
|
||||
public static final DataSourceColumn<Integer> HAS_SESSION = createInteger(
|
||||
DatabaseSettings.MYSQL_COL_HASSESSION);
|
||||
|
||||
|
||||
private AuthMeColumns() {
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package fr.xephi.authme.datasource.columnshandler;
|
||||
|
||||
import ch.jalu.configme.properties.Property;
|
||||
import ch.jalu.datasourcecolumns.ColumnType;
|
||||
import ch.jalu.datasourcecolumns.StandardTypes;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Util class for initializing {@link DataSourceColumn} objects.
|
||||
*/
|
||||
final class AuthMeColumnsFactory {
|
||||
|
||||
private AuthMeColumnsFactory() {
|
||||
}
|
||||
|
||||
static DataSourceColumn<Integer> createInteger(Property<String> nameProperty,
|
||||
ColumnOptions... options) {
|
||||
return new DataSourceColumn<>(StandardTypes.INTEGER, nameProperty,
|
||||
isOptional(options), hasDefaultForNull(options));
|
||||
}
|
||||
|
||||
static PlayerAuthColumn<Integer> createInteger(Property<String> nameProperty,
|
||||
Function<PlayerAuth, Integer> playerAuthGetter,
|
||||
ColumnOptions... options) {
|
||||
return createInternal(StandardTypes.INTEGER, nameProperty, playerAuthGetter, options);
|
||||
}
|
||||
|
||||
static PlayerAuthColumn<Long> createLong(Property<String> nameProperty,
|
||||
Function<PlayerAuth, Long> playerAuthGetter,
|
||||
ColumnOptions... options) {
|
||||
return createInternal(StandardTypes.LONG, nameProperty, playerAuthGetter, options);
|
||||
}
|
||||
|
||||
static PlayerAuthColumn<String> createString(Property<String> nameProperty,
|
||||
Function<PlayerAuth, String> playerAuthGetter,
|
||||
ColumnOptions... options) {
|
||||
return createInternal(StandardTypes.STRING, nameProperty, playerAuthGetter, options);
|
||||
}
|
||||
|
||||
static PlayerAuthColumn<Double> createDouble(Property<String> nameProperty,
|
||||
Function<PlayerAuth, Double> playerAuthGetter,
|
||||
ColumnOptions... options) {
|
||||
return createInternal(StandardTypes.DOUBLE, nameProperty, playerAuthGetter, options);
|
||||
}
|
||||
|
||||
static PlayerAuthColumn<Float> createFloat(Property<String> nameProperty,
|
||||
Function<PlayerAuth, Float> playerAuthGetter,
|
||||
ColumnOptions... options) {
|
||||
return createInternal(StandardTypes.FLOAT, nameProperty, playerAuthGetter, options);
|
||||
}
|
||||
|
||||
private static <T> PlayerAuthColumn<T> createInternal(ColumnType<T> type, Property<String> nameProperty,
|
||||
Function<PlayerAuth, T> authGetter,
|
||||
ColumnOptions... options) {
|
||||
return new PlayerAuthColumn<>(type, nameProperty, isOptional(options), hasDefaultForNull(options), authGetter);
|
||||
}
|
||||
|
||||
private static boolean isOptional(ColumnOptions[] options) {
|
||||
return containsInArray(ColumnOptions.OPTIONAL, options);
|
||||
}
|
||||
|
||||
private static boolean hasDefaultForNull(ColumnOptions[] options) {
|
||||
return containsInArray(ColumnOptions.DEFAULT_FOR_NULL, options);
|
||||
}
|
||||
|
||||
private static boolean containsInArray(ColumnOptions needle, ColumnOptions[] haystack) {
|
||||
for (ColumnOptions option : haystack) {
|
||||
if (option == needle) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
enum ColumnOptions {
|
||||
|
||||
OPTIONAL,
|
||||
|
||||
DEFAULT_FOR_NULL
|
||||
}
|
||||
}
|
@ -0,0 +1,206 @@
|
||||
package fr.xephi.authme.datasource.columnshandler;
|
||||
|
||||
import ch.jalu.datasourcecolumns.data.DataSourceValue;
|
||||
import ch.jalu.datasourcecolumns.data.DataSourceValues;
|
||||
import ch.jalu.datasourcecolumns.data.UpdateValues;
|
||||
import ch.jalu.datasourcecolumns.predicate.Predicate;
|
||||
import ch.jalu.datasourcecolumns.sqlimplementation.PredicateSqlGenerator;
|
||||
import ch.jalu.datasourcecolumns.sqlimplementation.SqlColumnsHandler;
|
||||
import ch.jalu.datasourcecolumns.sqlimplementation.statementgenerator.ConnectionSupplier;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.DatabaseSettings;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import static ch.jalu.datasourcecolumns.sqlimplementation.SqlColumnsHandlerConfig.forConnectionPool;
|
||||
import static ch.jalu.datasourcecolumns.sqlimplementation.SqlColumnsHandlerConfig.forSingleConnection;
|
||||
import static fr.xephi.authme.datasource.SqlDataSourceUtils.logSqlException;
|
||||
|
||||
/**
|
||||
* Wrapper of {@link SqlColumnsHandler} for the AuthMe data table.
|
||||
* Wraps exceptions and provides better support for operations based on a {@link PlayerAuth} object.
|
||||
*/
|
||||
public final class AuthMeColumnsHandler {
|
||||
|
||||
private final SqlColumnsHandler<ColumnContext, String> internalHandler;
|
||||
|
||||
private AuthMeColumnsHandler(SqlColumnsHandler<ColumnContext, String> internalHandler) {
|
||||
this.internalHandler = internalHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a column handler for SQLite.
|
||||
*
|
||||
* @param connection the connection to the database
|
||||
* @param settings plugin settings
|
||||
* @return created column handler
|
||||
*/
|
||||
public static AuthMeColumnsHandler createForSqlite(Connection connection, Settings settings) {
|
||||
ColumnContext columnContext = new ColumnContext(settings, false);
|
||||
String tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
|
||||
String nameColumn = settings.getProperty(DatabaseSettings.MYSQL_COL_NAME);
|
||||
|
||||
SqlColumnsHandler<ColumnContext, String> sqlColHandler = new SqlColumnsHandler<>(
|
||||
forSingleConnection(connection, tableName, nameColumn, columnContext)
|
||||
.setPredicateSqlGenerator(new PredicateSqlGenerator<>(columnContext, true))
|
||||
);
|
||||
return new AuthMeColumnsHandler(sqlColHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a column handler for MySQL.
|
||||
*
|
||||
* @param connectionSupplier supplier of connections from the connection pool
|
||||
* @param settings plugin settings
|
||||
* @return created column handler
|
||||
*/
|
||||
public static AuthMeColumnsHandler createForMySql(ConnectionSupplier connectionSupplier, Settings settings) {
|
||||
ColumnContext columnContext = new ColumnContext(settings, true);
|
||||
String tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
|
||||
String nameColumn = settings.getProperty(DatabaseSettings.MYSQL_COL_NAME);
|
||||
|
||||
SqlColumnsHandler<ColumnContext, String> sqlColHandler = new SqlColumnsHandler<>(
|
||||
forConnectionPool(connectionSupplier, tableName, nameColumn, columnContext));
|
||||
return new AuthMeColumnsHandler(sqlColHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a column from a specific row to the given value.
|
||||
*
|
||||
* @param name name of the account to modify
|
||||
* @param column the column to modify
|
||||
* @param value the value to set the column to
|
||||
* @param <T> the column type
|
||||
* @return true upon success, false otherwise
|
||||
*/
|
||||
public <T> boolean update(String name, DataSourceColumn<T> column, T value) {
|
||||
try {
|
||||
return internalHandler.update(name, column, value);
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a row to have the values as retrieved from the PlayerAuth object.
|
||||
*
|
||||
* @param auth the player auth object to modify and to get values from
|
||||
* @param columns the columns to update in the row
|
||||
* @return true upon success, false otherwise
|
||||
*/
|
||||
public boolean update(PlayerAuth auth, PlayerAuthColumn<?>... columns) {
|
||||
try {
|
||||
return internalHandler.update(auth.getNickname(), auth, columns);
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a row to have the given values.
|
||||
*
|
||||
* @param name the name of the account to modify
|
||||
* @param updateValues the values to set on the row
|
||||
* @return true upon success, false otherwise
|
||||
*/
|
||||
public boolean update(String name, UpdateValues<ColumnContext> updateValues) {
|
||||
try {
|
||||
return internalHandler.update(name.toLowerCase(), updateValues);
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given value to the provided column for all rows which match the predicate.
|
||||
*
|
||||
* @param predicate the predicate to filter rows by
|
||||
* @param column the column to modify on the matched rows
|
||||
* @param value the new value to set
|
||||
* @param <T> the column type
|
||||
* @return number of modified rows
|
||||
*/
|
||||
public <T> int update(Predicate<ColumnContext> predicate, DataSourceColumn<T> column, T value) {
|
||||
try {
|
||||
return internalHandler.update(predicate, column, value);
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given column from a given row.
|
||||
*
|
||||
* @param name the account name to look up
|
||||
* @param column the column whose value should be retrieved
|
||||
* @param <T> the column type
|
||||
* @return the result of the lookup
|
||||
* @throws SQLException .
|
||||
*/
|
||||
public <T> DataSourceValue<T> retrieve(String name, DataSourceColumn<T> column) throws SQLException {
|
||||
return internalHandler.retrieve(name.toLowerCase(), column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves multiple values from a given row.
|
||||
*
|
||||
* @param name the account name to look up
|
||||
* @param columns the columns to retrieve
|
||||
* @return map-like object with the requested values
|
||||
* @throws SQLException .
|
||||
*/
|
||||
public DataSourceValues retrieve(String name, DataSourceColumn<?>... columns) throws SQLException {
|
||||
return internalHandler.retrieve(name.toLowerCase(), columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a column's value for all rows that satisfy the given predicate.
|
||||
*
|
||||
* @param predicate the predicate to fulfill
|
||||
* @param column the column to retrieve from the matching rows
|
||||
* @param <T> the column's value type
|
||||
* @return the values of the matching rows
|
||||
* @throws SQLException .
|
||||
*/
|
||||
public <T> List<T> retrieve(Predicate<ColumnContext> predicate, DataSourceColumn<T> column) throws SQLException {
|
||||
return internalHandler.retrieve(predicate, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given values into a new row, as taken from the player auth.
|
||||
*
|
||||
* @param auth the player auth to get values from
|
||||
* @param columns the columns to insert
|
||||
* @return true upon success, false otherwise
|
||||
*/
|
||||
public boolean insert(PlayerAuth auth, PlayerAuthColumn<?>... columns) {
|
||||
try {
|
||||
return internalHandler.insert(auth, columns);
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of rows that match the provided predicate.
|
||||
*
|
||||
* @param predicate the predicate to test the rows for
|
||||
* @return number of rows fulfilling the predicate
|
||||
*/
|
||||
public int count(Predicate<ColumnContext> predicate) {
|
||||
try {
|
||||
return internalHandler.count(predicate);
|
||||
} catch (SQLException e) {
|
||||
logSqlException(e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package fr.xephi.authme.datasource.columnshandler;
|
||||
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Context for resolving the properties of {@link AuthMeColumns} entries.
|
||||
*/
|
||||
public class ColumnContext {
|
||||
|
||||
private final Settings settings;
|
||||
private final Map<DataSourceColumn<?>, String> columnNames = new HashMap<>();
|
||||
private final boolean hasDefaultSupport;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param settings plugin settings
|
||||
* @param hasDefaultSupport whether or not the underlying database has support for the {@code DEFAULT} keyword
|
||||
*/
|
||||
public ColumnContext(Settings settings, boolean hasDefaultSupport) {
|
||||
this.settings = settings;
|
||||
this.hasDefaultSupport = hasDefaultSupport;
|
||||
}
|
||||
|
||||
public String getName(DataSourceColumn<?> column) {
|
||||
return columnNames.computeIfAbsent(column, k -> settings.getProperty(k.getNameProperty()));
|
||||
}
|
||||
|
||||
public boolean hasDefaultSupport() {
|
||||
return hasDefaultSupport;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package fr.xephi.authme.datasource.columnshandler;
|
||||
|
||||
import ch.jalu.configme.properties.Property;
|
||||
import ch.jalu.datasourcecolumns.Column;
|
||||
import ch.jalu.datasourcecolumns.ColumnType;
|
||||
|
||||
/**
|
||||
* Basic {@link Column} implementation for AuthMe.
|
||||
*
|
||||
* @param <T> column type
|
||||
*/
|
||||
public class DataSourceColumn<T> implements Column<T, ColumnContext> {
|
||||
|
||||
private final ColumnType<T> columnType;
|
||||
private final Property<String> nameProperty;
|
||||
private final boolean isOptional;
|
||||
private final boolean useDefaultForNull;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param type type of the column
|
||||
* @param nameProperty property defining the column name
|
||||
* @param isOptional whether or not the column can be skipped (if name is configured to empty string)
|
||||
* @param useDefaultForNull whether SQL DEFAULT should be used for null values (if supported by the database)
|
||||
*/
|
||||
DataSourceColumn(ColumnType<T> type, Property<String> nameProperty, boolean isOptional, boolean useDefaultForNull) {
|
||||
this.columnType = type;
|
||||
this.nameProperty = nameProperty;
|
||||
this.isOptional = isOptional;
|
||||
this.useDefaultForNull = useDefaultForNull;
|
||||
}
|
||||
|
||||
public Property<String> getNameProperty() {
|
||||
return nameProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveName(ColumnContext columnContext) {
|
||||
return columnContext.getName(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnType<T> getType() {
|
||||
return columnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isColumnUsed(ColumnContext columnContext) {
|
||||
return !isOptional || !resolveName(columnContext).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useDefaultForNullValue(ColumnContext columnContext) {
|
||||
return useDefaultForNull && columnContext.hasDefaultSupport();
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package fr.xephi.authme.datasource.columnshandler;
|
||||
|
||||
import ch.jalu.configme.properties.Property;
|
||||
import ch.jalu.datasourcecolumns.ColumnType;
|
||||
import ch.jalu.datasourcecolumns.DependentColumn;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implementation for columns which can also be retrieved from a {@link PlayerAuth} object.
|
||||
*
|
||||
* @param <T> column type
|
||||
*/
|
||||
public class PlayerAuthColumn<T> extends DataSourceColumn<T> implements DependentColumn<T, ColumnContext, PlayerAuth> {
|
||||
|
||||
private final Function<PlayerAuth, T> playerAuthGetter;
|
||||
|
||||
/*
|
||||
* Constructor. See parent class for details.
|
||||
*/
|
||||
PlayerAuthColumn(ColumnType<T> type, Property<String> nameProperty, boolean isOptional, boolean useDefaultForNull,
|
||||
Function<PlayerAuth, T> playerAuthGetter) {
|
||||
super(type, nameProperty, isOptional, useDefaultForNull);
|
||||
this.playerAuthGetter = playerAuthGetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getValueFromDependent(PlayerAuth auth) {
|
||||
return playerAuthGetter.apply(auth);
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package fr.xephi.authme.events;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* This event is called when a player uses the register command,
|
||||
* it's fired even when a user does a /register with invalid arguments.
|
||||
* {@link #setCanRegister(boolean) event.setCanRegister(false)} prevents the player from registering.
|
||||
*/
|
||||
public class AuthMeAsyncPreRegisterEvent extends CustomEvent {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private final Player player;
|
||||
private boolean canRegister = true;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param player The player
|
||||
* @param isAsync True if the event is async, false otherwise
|
||||
*/
|
||||
public AuthMeAsyncPreRegisterEvent(Player player, boolean isAsync) {
|
||||
super(isAsync);
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the player concerned by this event.
|
||||
*
|
||||
* @return The player who executed a valid {@code /login} command
|
||||
*/
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the player is allowed to register.
|
||||
*
|
||||
* @return True if the player can log in, false otherwise
|
||||
*/
|
||||
public boolean canRegister() {
|
||||
return canRegister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define whether or not the player may register.
|
||||
*
|
||||
* @param canRegister True to allow the player to log in; false to prevent him
|
||||
*/
|
||||
public void setCanRegister(boolean canRegister) {
|
||||
this.canRegister = canRegister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of handlers, equivalent to {@link #getHandlers()} and required by {@link Event}.
|
||||
*
|
||||
* @return The list of handlers
|
||||
*/
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
}
|
87
src/main/java/fr/xephi/authme/events/EmailChangedEvent.java
Normal file
87
src/main/java/fr/xephi/authme/events/EmailChangedEvent.java
Normal file
@ -0,0 +1,87 @@
|
||||
package fr.xephi.authme.events;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This event is called when a player adds or changes his email address.
|
||||
*/
|
||||
public class EmailChangedEvent extends CustomEvent implements Cancellable {
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private final Player player;
|
||||
private final String oldEmail;
|
||||
private final String newEmail;
|
||||
private boolean isCancelled;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param player The player that changed email
|
||||
* @param oldEmail Old email player had on file. Can be null when user adds an email
|
||||
* @param newEmail New email that player tries to set. In case of adding email, this will contain
|
||||
* the email is trying to set.
|
||||
* @param isAsync should this event be called asynchronously?
|
||||
*/
|
||||
public EmailChangedEvent(Player player, @Nullable String oldEmail, String newEmail, boolean isAsync) {
|
||||
super(isAsync);
|
||||
this.player = player;
|
||||
this.oldEmail = oldEmail;
|
||||
this.newEmail = newEmail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return isCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player who changes the email
|
||||
*
|
||||
* @return The player who changed the email
|
||||
*/
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the old email in case user tries to change existing email.
|
||||
*
|
||||
* @return old email stored on file. Can be null when user never had an email and adds a new one.
|
||||
*/
|
||||
public @Nullable String getOldEmail() {
|
||||
return this.oldEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the new email.
|
||||
*
|
||||
* @return the email user is trying to set. If user adds email and never had one before,
|
||||
* this is where such email can be found.
|
||||
*/
|
||||
public String getNewEmail() {
|
||||
return this.newEmail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled) {
|
||||
this.isCancelled = cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of handlers, equivalent to {@link #getHandlers()} and required by {@link Event}.
|
||||
*
|
||||
* @return The list of handlers
|
||||
*/
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
}
|
47
src/main/java/fr/xephi/authme/events/RegisterEvent.java
Normal file
47
src/main/java/fr/xephi/authme/events/RegisterEvent.java
Normal file
@ -0,0 +1,47 @@
|
||||
package fr.xephi.authme.events;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
/**
|
||||
* Event fired when a player has successfully registered.
|
||||
*/
|
||||
public class RegisterEvent extends CustomEvent {
|
||||
|
||||
private static final HandlerList handlers = new HandlerList();
|
||||
private final Player player;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param player The player
|
||||
*/
|
||||
public RegisterEvent(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the player that has successfully logged in or registered.
|
||||
*
|
||||
* @return The player
|
||||
*/
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of handlers, equivalent to {@link #getHandlers()} and required by {@link Event}.
|
||||
*
|
||||
* @return The list of handlers
|
||||
*/
|
||||
public static HandlerList getHandlerList() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
package fr.xephi.authme.listener;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.QuickCommandsProtectionManager;
|
||||
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.permission.PermissionsManager;
|
||||
import fr.xephi.authme.permission.PlayerStatePermission;
|
||||
import fr.xephi.authme.permission.handlers.PermissionLoadUserException;
|
||||
import fr.xephi.authme.process.Management;
|
||||
import fr.xephi.authme.service.AntiBotService;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
@ -51,6 +54,8 @@ import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.event.player.PlayerShearEntityEvent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_MOVEMENT_RADIUS;
|
||||
import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOW_UNAUTHED_MOVEMENT;
|
||||
@ -63,7 +68,7 @@ public class PlayerListener implements Listener {
|
||||
@Inject
|
||||
private Settings settings;
|
||||
@Inject
|
||||
private Messages m;
|
||||
private Messages messages;
|
||||
@Inject
|
||||
private DataSource dataSource;
|
||||
@Inject
|
||||
@ -92,6 +97,7 @@ public class PlayerListener implements Listener {
|
||||
private BungeeSender bungeeSender;
|
||||
|
||||
private boolean isAsyncPlayerPreLoginEventCalled = false;
|
||||
private Set<String> unresolvedPlayerHostname = new HashSet<>();
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
|
||||
public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) {
|
||||
@ -105,12 +111,12 @@ public class PlayerListener implements Listener {
|
||||
final Player player = event.getPlayer();
|
||||
if (!quickCommandsProtectionManager.isAllowed(player.getName())) {
|
||||
event.setCancelled(true);
|
||||
player.kickPlayer(m.retrieveSingle(player, MessageKey.QUICK_COMMAND_PROTECTION_KICK));
|
||||
player.kickPlayer(messages.retrieveSingle(player, MessageKey.QUICK_COMMAND_PROTECTION_KICK));
|
||||
return;
|
||||
}
|
||||
if (listenerService.shouldCancelEvent(player)) {
|
||||
event.setCancelled(true);
|
||||
m.send(player, MessageKey.DENIED_COMMAND);
|
||||
messages.send(player, MessageKey.DENIED_COMMAND);
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,10 +127,18 @@ public class PlayerListener implements Listener {
|
||||
}
|
||||
|
||||
final Player player = event.getPlayer();
|
||||
if (listenerService.shouldCancelEvent(player)) {
|
||||
final boolean mayPlayerSendChat = !listenerService.shouldCancelEvent(player)
|
||||
|| permissionsManager.hasPermission(player, PlayerStatePermission.ALLOW_CHAT_BEFORE_LOGIN);
|
||||
if (mayPlayerSendChat) {
|
||||
removeUnauthorizedRecipients(event);
|
||||
} else {
|
||||
event.setCancelled(true);
|
||||
m.send(player, MessageKey.DENIED_CHAT);
|
||||
} else if (settings.getProperty(RestrictionSettings.HIDE_CHAT)) {
|
||||
messages.send(player, MessageKey.DENIED_CHAT);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeUnauthorizedRecipients(AsyncPlayerChatEvent event) {
|
||||
if (settings.getProperty(RestrictionSettings.HIDE_CHAT)) {
|
||||
event.getRecipients().removeIf(listenerService::shouldCancelEvent);
|
||||
if (event.getRecipients().isEmpty()) {
|
||||
event.setCancelled(true);
|
||||
@ -252,6 +266,15 @@ public class PlayerListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
// getAddress() sometimes returning null if not yet resolved
|
||||
// skip it and let PlayerLoginEvent to handle it
|
||||
if (event.getAddress() == null) {
|
||||
unresolvedPlayerHostname.add(event.getName());
|
||||
return;
|
||||
} else {
|
||||
unresolvedPlayerHostname.remove(event.getName());
|
||||
}
|
||||
|
||||
final String name = event.getName();
|
||||
|
||||
if (validationService.isUnrestricted(name)) {
|
||||
@ -260,15 +283,19 @@ public class PlayerListener implements Listener {
|
||||
|
||||
// Keep pre-UUID compatibility
|
||||
try {
|
||||
permissionsManager.loadUserData(event.getUniqueId());
|
||||
} catch (NoSuchMethodError e) {
|
||||
permissionsManager.loadUserData(name);
|
||||
try {
|
||||
permissionsManager.loadUserData(event.getUniqueId());
|
||||
} catch (NoSuchMethodError e) {
|
||||
permissionsManager.loadUserData(name);
|
||||
}
|
||||
} catch (PermissionLoadUserException e) {
|
||||
ConsoleLogger.logException("Unable to load the permission data of user " + name, e);
|
||||
}
|
||||
|
||||
try {
|
||||
runOnJoinChecks(JoiningPlayer.fromName(name), event.getAddress().getHostAddress());
|
||||
} catch (FailedVerificationException e) {
|
||||
event.setKickMessage(m.retrieveSingle(name, e.getReason(), e.getArgs()));
|
||||
event.setKickMessage(messages.retrieveSingle(name, e.getReason(), e.getArgs()));
|
||||
event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER);
|
||||
}
|
||||
}
|
||||
@ -292,11 +319,13 @@ public class PlayerListener implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAsyncPlayerPreLoginEventCalled || !settings.getProperty(PluginSettings.USE_ASYNC_PRE_LOGIN_EVENT)) {
|
||||
|
||||
if (!isAsyncPlayerPreLoginEventCalled || !settings.getProperty(PluginSettings.USE_ASYNC_PRE_LOGIN_EVENT)
|
||||
|| unresolvedPlayerHostname.remove(name)) {
|
||||
try {
|
||||
runOnJoinChecks(JoiningPlayer.fromPlayerObject(player), event.getAddress().getHostAddress());
|
||||
} catch (FailedVerificationException e) {
|
||||
event.setKickMessage(m.retrieveSingle(player, e.getReason(), e.getArgs()));
|
||||
event.setKickMessage(messages.retrieveSingle(player, e.getReason(), e.getArgs()));
|
||||
event.setResult(PlayerLoginEvent.Result.KICK_OTHER);
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ public enum MessageKey {
|
||||
/** Forgot your password? Please use the command: /email recovery <yourEmail> */
|
||||
FORGOT_PASSWORD_MESSAGE("recovery.forgot_password_hint"),
|
||||
|
||||
/** To login you have to solve a captcha code, please use the command: /captcha %captcha_code */
|
||||
/** To log in you have to solve a captcha code, please use the command: /captcha %captcha_code */
|
||||
USAGE_CAPTCHA("captcha.usage_captcha", "%captcha_code"),
|
||||
|
||||
/** Wrong captcha, please type "/captcha %captcha_code" into the chat! */
|
||||
@ -134,7 +134,7 @@ public enum MessageKey {
|
||||
/** Captcha code solved correctly! */
|
||||
CAPTCHA_SUCCESS("captcha.valid_captcha"),
|
||||
|
||||
/** To register you have to solve a captcha code first, please use the command: /captcha %captcha_code */
|
||||
/** To register you have to solve a captcha first, please use the command: /captcha %captcha_code */
|
||||
CAPTCHA_FOR_REGISTRATION_REQUIRED("captcha.captcha_for_registration", "%captcha_code"),
|
||||
|
||||
/** Valid captcha! You may now register with /register */
|
||||
@ -167,12 +167,18 @@ public enum MessageKey {
|
||||
/** Email address successfully added to your account! */
|
||||
EMAIL_ADDED_SUCCESS("email.added"),
|
||||
|
||||
/** Adding email was not allowed */
|
||||
EMAIL_ADD_NOT_ALLOWED("email.add_not_allowed"),
|
||||
|
||||
/** Please confirm your email address! */
|
||||
CONFIRM_EMAIL_MESSAGE("email.request_confirmation"),
|
||||
|
||||
/** Email address changed correctly! */
|
||||
EMAIL_CHANGED_SUCCESS("email.changed"),
|
||||
|
||||
/** Changing email was not allowed */
|
||||
EMAIL_CHANGE_NOT_ALLOWED("email.change_not_allowed"),
|
||||
|
||||
/** Your current email address is: %email */
|
||||
EMAIL_SHOW("email.email_show", "%email"),
|
||||
|
||||
@ -195,7 +201,34 @@ public enum MessageKey {
|
||||
EMAIL_ALREADY_USED_ERROR("email.already_used"),
|
||||
|
||||
/** Your secret code is %code. You can scan it from here %url */
|
||||
TWO_FACTOR_CREATE("misc.two_factor_create", "%code", "%url"),
|
||||
TWO_FACTOR_CREATE("two_factor.code_created", "%code", "%url"),
|
||||
|
||||
/** Please confirm your code with /2fa confirm <code> */
|
||||
TWO_FACTOR_CREATE_CONFIRMATION_REQUIRED("two_factor.confirmation_required"),
|
||||
|
||||
/** Please submit your two-factor authentication code with /2fa code <code> */
|
||||
TWO_FACTOR_CODE_REQUIRED("two_factor.code_required"),
|
||||
|
||||
/** Two-factor authentication is already enabled for your account! */
|
||||
TWO_FACTOR_ALREADY_ENABLED("two_factor.already_enabled"),
|
||||
|
||||
/** No 2fa key has been generated for you or it has expired. Please run /2fa add */
|
||||
TWO_FACTOR_ENABLE_ERROR_NO_CODE("two_factor.enable_error_no_code"),
|
||||
|
||||
/** Successfully enabled two-factor authentication for your account */
|
||||
TWO_FACTOR_ENABLE_SUCCESS("two_factor.enable_success"),
|
||||
|
||||
/** Wrong code or code has expired. Please run /2fa add */
|
||||
TWO_FACTOR_ENABLE_ERROR_WRONG_CODE("two_factor.enable_error_wrong_code"),
|
||||
|
||||
/** Two-factor authentication is not enabled for your account. Run /2fa add */
|
||||
TWO_FACTOR_NOT_ENABLED_ERROR("two_factor.not_enabled_error"),
|
||||
|
||||
/** Successfully removed two-factor auth from your account */
|
||||
TWO_FACTOR_REMOVED_SUCCESS("two_factor.removed_success"),
|
||||
|
||||
/** Invalid code! */
|
||||
TWO_FACTOR_INVALID_CODE("two_factor.invalid_code"),
|
||||
|
||||
/** You are not the owner of this account. Please choose another name! */
|
||||
NOT_OWNER_ERROR("on_join_validation.not_owner_error"),
|
||||
@ -246,27 +279,21 @@ public enum MessageKey {
|
||||
EMAIL_COOLDOWN_ERROR("email.email_cooldown_error", "%time"),
|
||||
|
||||
/**
|
||||
* The command you are trying to execute is sensitive and requires a verification!
|
||||
* A verification code has been sent to your email,
|
||||
* run the command "/verification [code]" to verify your identity.
|
||||
* This command is sensitive and requires an email verification!
|
||||
* Check your inbox and follow the email's instructions.
|
||||
*/
|
||||
VERIFICATION_CODE_REQUIRED("verification.code_required"),
|
||||
|
||||
/** Usage: /verification <code> */
|
||||
USAGE_VERIFICATION_CODE("verification.command_usage"),
|
||||
|
||||
/** Incorrect code, please type "/verification <code>" into the chat! */
|
||||
/** Incorrect code, please type "/verification <code>" into the chat, using the code you received by email */
|
||||
INCORRECT_VERIFICATION_CODE("verification.incorrect_code"),
|
||||
|
||||
/**
|
||||
* Your identity has been verified!
|
||||
* You can now execute every sensitive command within the current session!
|
||||
*/
|
||||
/** Your identity has been verified! You can now execute all commands within the current session! */
|
||||
VERIFICATION_CODE_VERIFIED("verification.success"),
|
||||
|
||||
/**
|
||||
* You can already execute every sensitive command within the current session!
|
||||
*/
|
||||
/** You can already execute every sensitive command within the current session! */
|
||||
VERIFICATION_CODE_ALREADY_VERIFIED("verification.already_verified"),
|
||||
|
||||
/** Your code has expired! Execute another sensitive command to get a new code! */
|
||||
|
@ -5,7 +5,7 @@ import ch.jalu.configme.configurationdata.ConfigurationData;
|
||||
import ch.jalu.configme.configurationdata.PropertyListBuilder;
|
||||
import ch.jalu.configme.properties.Property;
|
||||
import ch.jalu.configme.properties.StringProperty;
|
||||
import ch.jalu.configme.resource.YamlFileResource;
|
||||
import ch.jalu.configme.resource.PropertyResource;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.Files;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
@ -57,14 +57,16 @@ public class MessageUpdater {
|
||||
*/
|
||||
private boolean migrateAndSave(File userFile, JarMessageSource jarMessageSource) {
|
||||
// YamlConfiguration escapes all special characters when saving, making the file hard to use, so use ConfigMe
|
||||
YamlFileResource userResource = new MigraterYamlFileResource(userFile);
|
||||
PropertyResource userResource = new MigraterYamlFileResource(userFile);
|
||||
|
||||
// Step 1: Migrate any old keys in the file to the new paths
|
||||
boolean movedOldKeys = migrateOldKeys(userResource);
|
||||
// Step 2: Take any missing messages from the message files shipped in the AuthMe JAR
|
||||
// Step 2: Perform newer migrations
|
||||
boolean movedNewerKeys = migrateKeys(userResource);
|
||||
// Step 3: Take any missing messages from the message files shipped in the AuthMe JAR
|
||||
boolean addedMissingKeys = addMissingKeys(jarMessageSource, userResource);
|
||||
|
||||
if (movedOldKeys || addedMissingKeys) {
|
||||
if (movedOldKeys || movedNewerKeys || addedMissingKeys) {
|
||||
backupMessagesFile(userFile);
|
||||
|
||||
SettingsManager settingsManager = new SettingsManager(userResource, null, CONFIGURATION_DATA);
|
||||
@ -75,7 +77,19 @@ public class MessageUpdater {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean migrateOldKeys(YamlFileResource userResource) {
|
||||
private boolean migrateKeys(PropertyResource userResource) {
|
||||
return moveIfApplicable(userResource, "misc.two_factor_create", MessageKey.TWO_FACTOR_CREATE.getKey());
|
||||
}
|
||||
|
||||
private static boolean moveIfApplicable(PropertyResource resource, String oldPath, String newPath) {
|
||||
if (resource.getString(newPath) == null && resource.getString(oldPath) != null) {
|
||||
resource.setValue(newPath, resource.getString(oldPath));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean migrateOldKeys(PropertyResource userResource) {
|
||||
boolean hasChange = OldMessageKeysMigrater.migrateOldPaths(userResource);
|
||||
if (hasChange) {
|
||||
ConsoleLogger.info("Old keys have been moved to the new ones in your messages_xx.yml file");
|
||||
@ -83,7 +97,7 @@ public class MessageUpdater {
|
||||
return hasChange;
|
||||
}
|
||||
|
||||
private boolean addMissingKeys(JarMessageSource jarMessageSource, YamlFileResource userResource) {
|
||||
private boolean addMissingKeys(JarMessageSource jarMessageSource, PropertyResource userResource) {
|
||||
List<String> addedKeys = new ArrayList<>();
|
||||
for (Property<?> property : CONFIGURATION_DATA.getProperties()) {
|
||||
final String key = property.getPath();
|
||||
@ -131,6 +145,7 @@ public class MessageUpdater {
|
||||
.put("captcha", new String[]{"Captcha"})
|
||||
.put("verification", new String[]{"Verification code"})
|
||||
.put("time", new String[]{"Time units"})
|
||||
.put("two_factor", new String[]{"Two-factor authentication"})
|
||||
.build();
|
||||
|
||||
Set<String> addedKeys = new HashSet<>();
|
||||
|
@ -8,6 +8,7 @@ import fr.xephi.authme.permission.handlers.BPermissionsHandler;
|
||||
import fr.xephi.authme.permission.handlers.LuckPermsHandler;
|
||||
import fr.xephi.authme.permission.handlers.PermissionHandler;
|
||||
import fr.xephi.authme.permission.handlers.PermissionHandlerException;
|
||||
import fr.xephi.authme.permission.handlers.PermissionLoadUserException;
|
||||
import fr.xephi.authme.permission.handlers.PermissionsExHandler;
|
||||
import fr.xephi.authme.permission.handlers.VaultHandler;
|
||||
import fr.xephi.authme.permission.handlers.ZPermissionsHandler;
|
||||
@ -110,7 +111,9 @@ public class PermissionsManager implements Reloadable {
|
||||
* Creates a permission handler for the provided permission systems if possible.
|
||||
*
|
||||
* @param type the permission systems type for which to create a corresponding permission handler
|
||||
*
|
||||
* @return the permission handler, or {@code null} if not possible
|
||||
*
|
||||
* @throws PermissionHandlerException during initialization of the permission handler
|
||||
*/
|
||||
private PermissionHandler createPermissionHandler(PermissionsSystemType type) throws PermissionHandlerException {
|
||||
@ -228,8 +231,9 @@ public class PermissionsManager implements Reloadable {
|
||||
/**
|
||||
* Check if the given player has permission for the given permission node.
|
||||
*
|
||||
* @param joiningPlayer The player to check
|
||||
* @param joiningPlayer The player to check
|
||||
* @param permissionNode The permission node to verify
|
||||
*
|
||||
* @return true if the player has permission, false otherwise
|
||||
*/
|
||||
public boolean hasPermission(JoiningPlayer joiningPlayer, PermissionNode permissionNode) {
|
||||
@ -262,7 +266,7 @@ public class PermissionsManager implements Reloadable {
|
||||
* Check whether the offline player with the given name has permission for the given permission node.
|
||||
* This method is used as a last resort when nothing besides the name is known.
|
||||
*
|
||||
* @param name The name of the player
|
||||
* @param name The name of the player
|
||||
* @param permissionNode The permission node to verify
|
||||
*
|
||||
* @return true if the player has permission, false otherwise
|
||||
@ -317,7 +321,7 @@ public class PermissionsManager implements Reloadable {
|
||||
* @param groupName The group name.
|
||||
*
|
||||
* @return True if the player is in the specified group, false otherwise.
|
||||
* False is also returned if groups aren't supported by the used permissions system.
|
||||
* False is also returned if groups aren't supported by the used permissions system.
|
||||
*/
|
||||
public boolean isInGroup(OfflinePlayer player, String groupName) {
|
||||
return isEnabled() && handler.isInGroup(player, groupName);
|
||||
@ -330,7 +334,7 @@ public class PermissionsManager implements Reloadable {
|
||||
* @param groupName The name of the group.
|
||||
*
|
||||
* @return True if succeed, false otherwise.
|
||||
* False is also returned if this feature isn't supported for the current permissions system.
|
||||
* False is also returned if this feature isn't supported for the current permissions system.
|
||||
*/
|
||||
public boolean addGroup(OfflinePlayer player, String groupName) {
|
||||
if (!isEnabled() || StringUtils.isEmpty(groupName)) {
|
||||
@ -346,7 +350,7 @@ public class PermissionsManager implements Reloadable {
|
||||
* @param groupNames The name of the groups to add.
|
||||
*
|
||||
* @return True if at least one group was added, false otherwise.
|
||||
* False is also returned if this feature isn't supported for the current permissions system.
|
||||
* False is also returned if this feature isn't supported for the current permissions system.
|
||||
*/
|
||||
public boolean addGroups(OfflinePlayer player, Collection<String> groupNames) {
|
||||
// If no permissions system is used, return false
|
||||
@ -373,7 +377,7 @@ public class PermissionsManager implements Reloadable {
|
||||
* @param groupName The name of the group.
|
||||
*
|
||||
* @return True if succeed, false otherwise.
|
||||
* False is also returned if this feature isn't supported for the current permissions system.
|
||||
* False is also returned if this feature isn't supported for the current permissions system.
|
||||
*/
|
||||
public boolean removeGroup(OfflinePlayer player, String groupName) {
|
||||
return isEnabled() && handler.removeFromGroup(player, groupName);
|
||||
@ -386,7 +390,7 @@ public class PermissionsManager implements Reloadable {
|
||||
* @param groupNames The name of the groups to remove.
|
||||
*
|
||||
* @return True if at least one group was removed, false otherwise.
|
||||
* False is also returned if this feature isn't supported for the current permissions system.
|
||||
* False is also returned if this feature isn't supported for the current permissions system.
|
||||
*/
|
||||
public boolean removeGroups(OfflinePlayer player, Collection<String> groupNames) {
|
||||
// If no permissions system is used, return false
|
||||
@ -414,7 +418,7 @@ public class PermissionsManager implements Reloadable {
|
||||
* @param groupName The name of the group.
|
||||
*
|
||||
* @return True if succeed, false otherwise.
|
||||
* False is also returned if this feature isn't supported for the current permissions system.
|
||||
* False is also returned if this feature isn't supported for the current permissions system.
|
||||
*/
|
||||
public boolean setGroup(OfflinePlayer player, String groupName) {
|
||||
return isEnabled() && handler.setGroup(player, groupName);
|
||||
@ -428,7 +432,7 @@ public class PermissionsManager implements Reloadable {
|
||||
* @param player The player to remove all groups from.
|
||||
*
|
||||
* @return True if succeed, false otherwise.
|
||||
* False will also be returned if this feature isn't supported for the used permissions system.
|
||||
* False will also be returned if this feature isn't supported for the used permissions system.
|
||||
*/
|
||||
public boolean removeAllGroups(OfflinePlayer player) {
|
||||
// If no permissions system is used, return false
|
||||
@ -443,15 +447,47 @@ public class PermissionsManager implements Reloadable {
|
||||
return removeGroups(player, groupNames);
|
||||
}
|
||||
|
||||
public void loadUserData(UUID uuid) {
|
||||
if(!isEnabled()) {
|
||||
/**
|
||||
* Loads the permission data of the given player.
|
||||
*
|
||||
* @param offlinePlayer the offline player.
|
||||
* @return true if the load was successful.
|
||||
*/
|
||||
public boolean loadUserData(OfflinePlayer offlinePlayer) {
|
||||
try {
|
||||
try {
|
||||
loadUserData(offlinePlayer.getUniqueId());
|
||||
} catch (NoSuchMethodError e) {
|
||||
loadUserData(offlinePlayer.getName());
|
||||
}
|
||||
} catch (PermissionLoadUserException e) {
|
||||
ConsoleLogger.logException("Unable to load the permission data of user " + offlinePlayer.getName(), e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the permission data of the given player unique identifier.
|
||||
*
|
||||
* @param uuid the {@link UUID} of the player.
|
||||
* @throws PermissionLoadUserException if the action failed.
|
||||
*/
|
||||
public void loadUserData(UUID uuid) throws PermissionLoadUserException {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
handler.loadUserData(uuid);
|
||||
}
|
||||
|
||||
public void loadUserData(String name) {
|
||||
if(!isEnabled()) {
|
||||
/**
|
||||
* Loads the permission data of the given player name.
|
||||
*
|
||||
* @param name the name of the player.
|
||||
* @throws PermissionLoadUserException if the action failed.
|
||||
*/
|
||||
public void loadUserData(String name) throws PermissionLoadUserException {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
handler.loadUserData(name);
|
||||
|
@ -73,7 +73,17 @@ public enum PlayerPermission implements PermissionNode {
|
||||
/**
|
||||
* Permission that enables on join quick commands checks for the player.
|
||||
*/
|
||||
QUICK_COMMANDS_PROTECTION("authme.player.protection.quickcommandsprotection");
|
||||
QUICK_COMMANDS_PROTECTION("authme.player.protection.quickcommandsprotection"),
|
||||
|
||||
/**
|
||||
* Permission to enable two-factor authentication.
|
||||
*/
|
||||
ENABLE_TWO_FACTOR_AUTH("authme.player.totpadd"),
|
||||
|
||||
/**
|
||||
* Permission to disable two-factor authentication.
|
||||
*/
|
||||
DISABLE_TWO_FACTOR_AUTH("authme.player.totpremove");
|
||||
|
||||
/**
|
||||
* The permission node.
|
||||
|
@ -34,7 +34,12 @@ public enum PlayerStatePermission implements PermissionNode {
|
||||
/**
|
||||
* Permission to bypass the GeoIp country code check.
|
||||
*/
|
||||
BYPASS_COUNTRY_CHECK("authme.bypasscountrycheck", DefaultPermission.NOT_ALLOWED);
|
||||
BYPASS_COUNTRY_CHECK("authme.bypasscountrycheck", DefaultPermission.NOT_ALLOWED),
|
||||
|
||||
/**
|
||||
* Permission to send chat messages before being logged in.
|
||||
*/
|
||||
ALLOW_CHAT_BEFORE_LOGIN("authme.allowchatbeforelogin", DefaultPermission.NOT_ALLOWED);
|
||||
|
||||
/**
|
||||
* The permission node.
|
||||
@ -42,7 +47,7 @@ public enum PlayerStatePermission implements PermissionNode {
|
||||
private String node;
|
||||
|
||||
/**
|
||||
* The default permission level
|
||||
* The default permission level.
|
||||
*/
|
||||
private DefaultPermission defaultPermission;
|
||||
|
||||
|
@ -189,22 +189,21 @@ public class LuckPermsHandler implements PermissionHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadUserData(UUID uuid) {
|
||||
public void loadUserData(UUID uuid) throws PermissionLoadUserException {
|
||||
try {
|
||||
luckPermsApi.getUserManager().loadUser(uuid).get(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
e.printStackTrace();
|
||||
throw new PermissionLoadUserException("Unable to load the permission data of the user " + uuid, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadUserData(String name) {
|
||||
public void loadUserData(String name) throws PermissionLoadUserException {
|
||||
try {
|
||||
UUID uuid = luckPermsApi.getStorage().getUUID(name).get(5, TimeUnit.SECONDS);
|
||||
loadUserData(uuid);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
e.printStackTrace();
|
||||
throw new PermissionLoadUserException("Unable to load the permission data of the user " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -107,10 +107,9 @@ public interface PermissionHandler {
|
||||
*/
|
||||
PermissionsSystemType getPermissionSystem();
|
||||
|
||||
default void loadUserData(UUID uuid) {
|
||||
default void loadUserData(UUID uuid) throws PermissionLoadUserException {
|
||||
}
|
||||
|
||||
default void loadUserData(String name) {
|
||||
default void loadUserData(String name) throws PermissionLoadUserException {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
package fr.xephi.authme.permission.handlers;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Exception thrown when a {@link PermissionHandler#loadUserData(UUID uuid)} request fails.
|
||||
*/
|
||||
public class PermissionLoadUserException extends Exception {
|
||||
|
||||
public PermissionLoadUserException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -4,8 +4,10 @@ import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.data.auth.PlayerCache;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.events.EmailChangedEvent;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.process.AsynchronousProcess;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.service.CommonService;
|
||||
import fr.xephi.authme.service.ValidationService;
|
||||
import fr.xephi.authme.service.bungeecord.BungeeSender;
|
||||
@ -35,6 +37,9 @@ public class AsyncAddEmail implements AsynchronousProcess {
|
||||
@Inject
|
||||
private BungeeSender bungeeSender;
|
||||
|
||||
@Inject
|
||||
private BukkitService bukkitService;
|
||||
|
||||
AsyncAddEmail() { }
|
||||
|
||||
/**
|
||||
@ -57,6 +62,13 @@ public class AsyncAddEmail implements AsynchronousProcess {
|
||||
} else if (!validationService.isEmailFreeForRegistration(email, player)) {
|
||||
service.send(player, MessageKey.EMAIL_ALREADY_USED_ERROR);
|
||||
} else {
|
||||
EmailChangedEvent event = bukkitService.createAndCallEvent(isAsync
|
||||
-> new EmailChangedEvent(player, null, email, isAsync));
|
||||
if (event.isCancelled()) {
|
||||
ConsoleLogger.info("Could not add email to player '" + player + "' – event was cancelled");
|
||||
service.send(player, MessageKey.EMAIL_ADD_NOT_ALLOWED);
|
||||
return;
|
||||
}
|
||||
auth.setEmail(email);
|
||||
if (dataSource.updateEmail(auth)) {
|
||||
playerCache.updatePlayer(auth);
|
||||
|
@ -1,10 +1,13 @@
|
||||
package fr.xephi.authme.process.email;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.data.auth.PlayerCache;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.events.EmailChangedEvent;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.process.AsynchronousProcess;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.service.CommonService;
|
||||
import fr.xephi.authme.service.ValidationService;
|
||||
import fr.xephi.authme.service.bungeecord.BungeeSender;
|
||||
@ -32,6 +35,9 @@ public class AsyncChangeEmail implements AsynchronousProcess {
|
||||
|
||||
@Inject
|
||||
private BungeeSender bungeeSender;
|
||||
|
||||
@Inject
|
||||
private BukkitService bukkitService;
|
||||
|
||||
AsyncChangeEmail() { }
|
||||
|
||||
@ -57,14 +63,22 @@ public class AsyncChangeEmail implements AsynchronousProcess {
|
||||
} else if (!validationService.isEmailFreeForRegistration(newEmail, player)) {
|
||||
service.send(player, MessageKey.EMAIL_ALREADY_USED_ERROR);
|
||||
} else {
|
||||
saveNewEmail(auth, player, newEmail);
|
||||
saveNewEmail(auth, player, oldEmail, newEmail);
|
||||
}
|
||||
} else {
|
||||
outputUnloggedMessage(player);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveNewEmail(PlayerAuth auth, Player player, String newEmail) {
|
||||
private void saveNewEmail(PlayerAuth auth, Player player, String oldEmail, String newEmail) {
|
||||
EmailChangedEvent event = bukkitService.createAndCallEvent(isAsync
|
||||
-> new EmailChangedEvent(player, oldEmail, newEmail, isAsync));
|
||||
if (event.isCancelled()) {
|
||||
ConsoleLogger.info("Could not change email for player '" + player + "' – event was cancelled");
|
||||
service.send(player, MessageKey.EMAIL_CHANGE_NOT_ALLOWED);
|
||||
return;
|
||||
}
|
||||
|
||||
auth.setEmail(newEmail);
|
||||
if (dataSource.updateEmail(auth)) {
|
||||
playerCache.updatePlayer(auth);
|
||||
|
@ -18,6 +18,7 @@ import fr.xephi.authme.settings.commandconfig.CommandManager;
|
||||
import fr.xephi.authme.settings.properties.HooksSettings;
|
||||
import fr.xephi.authme.settings.properties.RegistrationSettings;
|
||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||
import fr.xephi.authme.util.InternetProtocolUtils;
|
||||
import fr.xephi.authme.util.PlayerUtils;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Server;
|
||||
@ -183,8 +184,7 @@ public class AsynchronousJoin implements AsynchronousProcess {
|
||||
private boolean validatePlayerCountForIp(final Player player, String ip) {
|
||||
if (service.getProperty(RestrictionSettings.MAX_JOIN_PER_IP) > 0
|
||||
&& !service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)
|
||||
&& !"127.0.0.1".equalsIgnoreCase(ip)
|
||||
&& !"localhost".equalsIgnoreCase(ip)
|
||||
&& !InternetProtocolUtils.isLoopbackAddress(ip)
|
||||
&& countOnlinePlayersByIp(ip) > service.getProperty(RestrictionSettings.MAX_JOIN_PER_IP)) {
|
||||
|
||||
bukkitService.scheduleSyncTaskFromOptionallyAsyncTask(
|
||||
|
@ -6,6 +6,8 @@ import fr.xephi.authme.data.TempbanManager;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.data.auth.PlayerCache;
|
||||
import fr.xephi.authme.data.captcha.LoginCaptchaManager;
|
||||
import fr.xephi.authme.data.limbo.LimboMessageType;
|
||||
import fr.xephi.authme.data.limbo.LimboPlayerState;
|
||||
import fr.xephi.authme.data.limbo.LimboService;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent;
|
||||
@ -28,6 +30,7 @@ import fr.xephi.authme.settings.properties.EmailSettings;
|
||||
import fr.xephi.authme.settings.properties.HooksSettings;
|
||||
import fr.xephi.authme.settings.properties.PluginSettings;
|
||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||
import fr.xephi.authme.util.InternetProtocolUtils;
|
||||
import fr.xephi.authme.util.PlayerUtils;
|
||||
import fr.xephi.authme.util.Utils;
|
||||
import org.bukkit.ChatColor;
|
||||
@ -90,7 +93,13 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
||||
public void login(Player player, String password) {
|
||||
PlayerAuth auth = getPlayerAuth(player);
|
||||
if (auth != null && checkPlayerInfo(player, auth, password)) {
|
||||
performLogin(player, auth);
|
||||
if (auth.getTotpKey() != null) {
|
||||
limboService.resetMessageTask(player, LimboMessageType.TOTP_CODE);
|
||||
limboService.getLimboPlayer(player.getName()).setState(LimboPlayerState.TOTP_REQUIRED);
|
||||
// TODO #1141: Check if we should check limbo state before processing password
|
||||
} else {
|
||||
performLogin(player, auth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +134,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
||||
if (auth == null) {
|
||||
service.send(player, MessageKey.UNKNOWN_USER);
|
||||
// Recreate the message task to immediately send the message again as response
|
||||
limboService.resetMessageTask(player, false);
|
||||
limboService.resetMessageTask(player, LimboMessageType.REGISTER);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -218,7 +227,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
||||
* @param player the player to log in
|
||||
* @param auth the associated PlayerAuth object
|
||||
*/
|
||||
private void performLogin(Player player, PlayerAuth auth) {
|
||||
public void performLogin(Player player, PlayerAuth auth) {
|
||||
if (player.isOnline()) {
|
||||
final boolean isFirstLogin = (auth.getLastLogin() == null);
|
||||
|
||||
@ -317,8 +326,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
|
||||
// Do not perform the check if player has multiple accounts permission or if IP is localhost
|
||||
if (service.getProperty(RestrictionSettings.MAX_LOGIN_PER_IP) <= 0
|
||||
|| service.hasPermission(player, PlayerStatePermission.ALLOW_MULTIPLE_ACCOUNTS)
|
||||
|| "127.0.0.1".equalsIgnoreCase(ip)
|
||||
|| "localhost".equalsIgnoreCase(ip)) {
|
||||
|| InternetProtocolUtils.isLoopbackAddress(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -4,16 +4,19 @@ import ch.jalu.injector.factory.SingletonStore;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.data.auth.PlayerCache;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.events.AuthMeAsyncPreRegisterEvent;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.process.AsynchronousProcess;
|
||||
import fr.xephi.authme.process.register.executors.RegistrationExecutor;
|
||||
import fr.xephi.authme.process.register.executors.RegistrationMethod;
|
||||
import fr.xephi.authme.process.register.executors.RegistrationParameters;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.service.CommonService;
|
||||
import fr.xephi.authme.service.bungeecord.BungeeSender;
|
||||
import fr.xephi.authme.service.bungeecord.MessageType;
|
||||
import fr.xephi.authme.settings.properties.RegistrationSettings;
|
||||
import fr.xephi.authme.settings.properties.RestrictionSettings;
|
||||
import fr.xephi.authme.util.InternetProtocolUtils;
|
||||
import fr.xephi.authme.util.PlayerUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@ -32,6 +35,8 @@ public class AsyncRegister implements AsynchronousProcess {
|
||||
@Inject
|
||||
private PlayerCache playerCache;
|
||||
@Inject
|
||||
private BukkitService bukkitService;
|
||||
@Inject
|
||||
private CommonService service;
|
||||
@Inject
|
||||
private SingletonStore<RegistrationExecutor> registrationExecutorFactory;
|
||||
@ -44,9 +49,9 @@ public class AsyncRegister implements AsynchronousProcess {
|
||||
/**
|
||||
* Performs the registration process for the given player.
|
||||
*
|
||||
* @param variant the registration method
|
||||
* @param variant the registration method
|
||||
* @param parameters the parameters
|
||||
* @param <P> parameters type
|
||||
* @param <P> parameters type
|
||||
*/
|
||||
public <P extends RegistrationParameters> void register(RegistrationMethod<P> variant, P parameters) {
|
||||
if (preRegisterCheck(variant, parameters.getPlayer())) {
|
||||
@ -57,6 +62,14 @@ public class AsyncRegister implements AsynchronousProcess {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the player is able to register, in that case the {@link AuthMeAsyncPreRegisterEvent} is invoked.
|
||||
*
|
||||
* @param variant the registration type variant.
|
||||
* @param player the player which is trying to register.
|
||||
*
|
||||
* @return true if the checks are successful and the event hasn't marked the action as denied, false otherwise.
|
||||
*/
|
||||
private boolean preRegisterCheck(RegistrationMethod<?> variant, Player player) {
|
||||
final String name = player.getName().toLowerCase();
|
||||
if (playerCache.isAuthenticated(name)) {
|
||||
@ -70,6 +83,12 @@ public class AsyncRegister implements AsynchronousProcess {
|
||||
return false;
|
||||
}
|
||||
|
||||
AuthMeAsyncPreRegisterEvent event = bukkitService.createAndCallEvent(
|
||||
isAsync -> new AuthMeAsyncPreRegisterEvent(player, isAsync));
|
||||
if (!event.canRegister()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return variant == RegistrationMethod.API_REGISTRATION || isPlayerIpAllowedToRegister(player);
|
||||
}
|
||||
|
||||
@ -77,11 +96,11 @@ public class AsyncRegister implements AsynchronousProcess {
|
||||
* Executes the registration.
|
||||
*
|
||||
* @param parameters the registration parameters
|
||||
* @param executor the executor to perform the registration process with
|
||||
* @param <P> registration params type
|
||||
* @param executor the executor to perform the registration process with
|
||||
* @param <P> registration params type
|
||||
*/
|
||||
private <P extends RegistrationParameters>
|
||||
void executeRegistration(P parameters, RegistrationExecutor<P> executor) {
|
||||
void executeRegistration(P parameters, RegistrationExecutor<P> executor) {
|
||||
PlayerAuth auth = executor.buildPlayerAuth(parameters);
|
||||
if (database.saveAuth(auth)) {
|
||||
executor.executePostPersistAction(parameters);
|
||||
@ -95,14 +114,14 @@ public class AsyncRegister implements AsynchronousProcess {
|
||||
* Checks whether the registration threshold has been exceeded for the given player's IP address.
|
||||
*
|
||||
* @param player the player to check
|
||||
*
|
||||
* @return true if registration may take place, false otherwise (IP check failed)
|
||||
*/
|
||||
private boolean isPlayerIpAllowedToRegister(Player player) {
|
||||
final int maxRegPerIp = service.getProperty(RestrictionSettings.MAX_REGISTRATION_PER_IP);
|
||||
final String ip = PlayerUtils.getPlayerIp(player);
|
||||
if (maxRegPerIp > 0
|
||||
&& !"127.0.0.1".equalsIgnoreCase(ip)
|
||||
&& !"localhost".equalsIgnoreCase(ip)
|
||||
&& !InternetProtocolUtils.isLoopbackAddress(ip)
|
||||
&& !service.hasPermission(player, ALLOW_MULTIPLE_ACCOUNTS)) {
|
||||
List<String> otherAccounts = database.getAllAuthsByIp(ip);
|
||||
if (otherAccounts.size() >= maxRegPerIp) {
|
||||
|
@ -2,8 +2,10 @@ package fr.xephi.authme.process.register;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.limbo.LimboService;
|
||||
import fr.xephi.authme.events.RegisterEvent;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.process.SynchronousProcess;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.service.CommonService;
|
||||
import fr.xephi.authme.util.PlayerUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -15,6 +17,9 @@ import javax.inject.Inject;
|
||||
*/
|
||||
public class ProcessSyncEmailRegister implements SynchronousProcess {
|
||||
|
||||
@Inject
|
||||
private BukkitService bukkitService;
|
||||
|
||||
@Inject
|
||||
private CommonService service;
|
||||
|
||||
@ -34,6 +39,7 @@ public class ProcessSyncEmailRegister implements SynchronousProcess {
|
||||
limboService.replaceTasksAfterRegistration(player);
|
||||
|
||||
player.saveData();
|
||||
bukkitService.callEvent(new RegisterEvent(player));
|
||||
ConsoleLogger.fine(player.getName() + " registered " + PlayerUtils.getPlayerIp(player));
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,10 @@ package fr.xephi.authme.process.register;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.limbo.LimboService;
|
||||
import fr.xephi.authme.events.RegisterEvent;
|
||||
import fr.xephi.authme.message.MessageKey;
|
||||
import fr.xephi.authme.process.SynchronousProcess;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.service.CommonService;
|
||||
import fr.xephi.authme.service.bungeecord.BungeeSender;
|
||||
import fr.xephi.authme.settings.commandconfig.CommandManager;
|
||||
@ -31,6 +33,9 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess {
|
||||
@Inject
|
||||
private CommandManager commandManager;
|
||||
|
||||
@Inject
|
||||
private BukkitService bukkitService;
|
||||
|
||||
ProcessSyncPasswordRegister() {
|
||||
}
|
||||
|
||||
@ -60,6 +65,7 @@ public class ProcessSyncPasswordRegister implements SynchronousProcess {
|
||||
}
|
||||
|
||||
player.saveData();
|
||||
bukkitService.callEvent(new RegisterEvent(player));
|
||||
ConsoleLogger.fine(player.getName() + " registered " + PlayerUtils.getPlayerIp(player));
|
||||
|
||||
// Kick Player after Registration is enabled, kick the player
|
||||
|
@ -1,6 +1,7 @@
|
||||
package fr.xephi.authme.security;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
@ -78,6 +79,20 @@ public final class HashUtils {
|
||||
return hash.length() > 3 && hash.substring(0, 2).equals("$2");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the two strings are equal to each other in a time-constant manner.
|
||||
* This helps to avoid timing side channel attacks,
|
||||
* cf. <a href="https://github.com/AuthMe/AuthMeReloaded/issues/1561">issue #1561</a>.
|
||||
*
|
||||
* @param string1 first string
|
||||
* @param string2 second string
|
||||
* @return true if the strings are equal to each other, false otherwise
|
||||
*/
|
||||
public static boolean isEqual(String string1, String string2) {
|
||||
return MessageDigest.isEqual(
|
||||
string1.getBytes(StandardCharsets.UTF_8), string2.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the message with the given algorithm and return the hash in its hexadecimal notation.
|
||||
*
|
||||
|
@ -8,7 +8,7 @@ import fr.xephi.authme.security.crypts.description.SaltType;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.HooksSettings;
|
||||
import fr.xephi.authme.util.StringUtils;
|
||||
import fr.xephi.authme.util.ExceptionUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@ -39,7 +39,7 @@ public class BCrypt implements EncryptionMethod {
|
||||
try {
|
||||
return HashUtils.isValidBcryptHash(hash.getHash()) && BCryptService.checkpw(password, hash.getHash());
|
||||
} catch (IllegalArgumentException e) {
|
||||
ConsoleLogger.warning("Bcrypt checkpw() returned " + StringUtils.formatException(e));
|
||||
ConsoleLogger.warning("Bcrypt checkpw() returned " + ExceptionUtils.formatException(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package fr.xephi.authme.security.crypts;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
@Recommendation(Usage.RECOMMENDED)
|
||||
public class BCrypt2y extends HexSaltedMethod {
|
||||
|
||||
@ -23,7 +25,7 @@ public class BCrypt2y extends HexSaltedMethod {
|
||||
// The salt is the first 29 characters of the hash
|
||||
|
||||
String salt = hash.substring(0, 29);
|
||||
return hash.equals(computeHash(password, salt, null));
|
||||
return isEqual(hash, computeHash(password, salt, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2,12 +2,12 @@ package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
import fr.xephi.authme.security.crypts.description.HasSalt;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
import fr.xephi.authme.util.StringUtils;
|
||||
import fr.xephi.authme.util.ExceptionUtils;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
|
||||
|
||||
/**
|
||||
@ -37,7 +37,7 @@ public class Ipb4 implements EncryptionMethod {
|
||||
try {
|
||||
return HashUtils.isValidBcryptHash(hash.getHash()) && BCryptService.checkpw(password, hash.getHash());
|
||||
} catch (IllegalArgumentException e) {
|
||||
ConsoleLogger.warning("Bcrypt checkpw() returned " + StringUtils.formatException(e));
|
||||
ConsoleLogger.warning("Bcrypt checkpw() returned " + ExceptionUtils.formatException(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
@Recommendation(Usage.ACCEPTABLE)
|
||||
public class Joomla extends HexSaltedMethod {
|
||||
|
||||
@ -16,7 +18,7 @@ public class Joomla extends HexSaltedMethod {
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String unusedName) {
|
||||
String hash = hashedPassword.getHash();
|
||||
String[] hashParts = hash.split(":");
|
||||
return hashParts.length == 2 && hash.equals(computeHash(password, hashParts[1], null));
|
||||
return hashParts.length == 2 && isEqual(hash, computeHash(password, hashParts[1], null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,5 +1,6 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
import static fr.xephi.authme.security.HashUtils.md5;
|
||||
|
||||
public class Md5vB extends HexSaltedMethod {
|
||||
@ -13,7 +14,7 @@ public class Md5vB extends HexSaltedMethod {
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
String hash = hashedPassword.getHash();
|
||||
String[] line = hash.split("\\$");
|
||||
return line.length == 4 && hash.equals(computeHash(password, line[2], name));
|
||||
return line.length == 4 && isEqual(hash, computeHash(password, line[2], name));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,5 +1,6 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import de.rtner.misc.BinTools;
|
||||
import de.rtner.security.auth.spi.PBKDF2Engine;
|
||||
import de.rtner.security.auth.spi.PBKDF2Parameters;
|
||||
@ -38,13 +39,12 @@ public class Pbkdf2 extends HexSaltedMethod {
|
||||
if (line.length != 4) {
|
||||
return false;
|
||||
}
|
||||
int iterations;
|
||||
try {
|
||||
iterations = Integer.parseInt(line[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
ConsoleLogger.logException("Cannot read number of rounds for Pbkdf2", e);
|
||||
Integer iterations = Ints.tryParse(line[1]);
|
||||
if (iterations == null) {
|
||||
ConsoleLogger.warning("Cannot read number of rounds for Pbkdf2: '" + line[1] + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
String salt = line[2];
|
||||
byte[] derivedKey = BinTools.hex2bin(line[3]);
|
||||
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "UTF-8", salt.getBytes(), iterations, derivedKey);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import de.rtner.security.auth.spi.PBKDF2Engine;
|
||||
import de.rtner.security.auth.spi.PBKDF2Parameters;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
@ -27,13 +28,12 @@ public class Pbkdf2Django extends HexSaltedMethod {
|
||||
if (line.length != 4) {
|
||||
return false;
|
||||
}
|
||||
int iterations;
|
||||
try {
|
||||
iterations = Integer.parseInt(line[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
ConsoleLogger.logException("Could not read number of rounds for Pbkdf2Django:", e);
|
||||
Integer iterations = Ints.tryParse(line[1]);
|
||||
if (iterations == null) {
|
||||
ConsoleLogger.warning("Cannot read number of rounds for Pbkdf2Django: '" + line[1] + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
String salt = line[2];
|
||||
byte[] derivedKey = Base64.getDecoder().decode(line[3]);
|
||||
PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), iterations, derivedKey);
|
||||
|
@ -10,6 +10,8 @@ import fr.xephi.authme.security.crypts.description.Usage;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
/**
|
||||
* Encryption method compatible with phpBB3.
|
||||
* <p>
|
||||
@ -43,7 +45,7 @@ public class PhpBB implements EncryptionMethod {
|
||||
} else if (hash.length() == 34) {
|
||||
return PhpassSaltedMd5.phpbb_check_hash(password, hash);
|
||||
} else {
|
||||
return PhpassSaltedMd5.md5(password).equals(hash);
|
||||
return isEqual(hash, PhpassSaltedMd5.md5(password));
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,7 +155,7 @@ public class PhpBB implements EncryptionMethod {
|
||||
}
|
||||
|
||||
private static boolean phpbb_check_hash(String password, String hash) {
|
||||
return _hash_crypt_private(password, hash).equals(hash);
|
||||
return isEqual(hash, _hash_crypt_private(password, hash)); // #1561: fix timing issue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package fr.xephi.authme.security.crypts;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
/**
|
||||
* Common supertype for encryption methods which store their salt separately from the hash.
|
||||
*/
|
||||
@ -19,7 +21,7 @@ public abstract class SeparateSaltMethod implements EncryptionMethod {
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
return hashedPassword.getHash().equals(computeHash(password, hashedPassword.getSalt(), null));
|
||||
return isEqual(hashedPassword.getHash(), computeHash(password, hashedPassword.getSalt(), null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3,6 +3,7 @@ package fr.xephi.authme.security.crypts;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
import static fr.xephi.authme.security.HashUtils.sha256;
|
||||
|
||||
@Recommendation(Usage.RECOMMENDED)
|
||||
@ -14,10 +15,10 @@ public class Sha256 extends HexSaltedMethod {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) {
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
String hash = hashedPassword.getHash();
|
||||
String[] line = hash.split("\\$");
|
||||
return line.length == 4 && hash.equals(computeHash(password, line[2], ""));
|
||||
return line.length == 4 && isEqual(hash, computeHash(password, line[2], name));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -7,6 +7,8 @@ import fr.xephi.authme.security.crypts.description.SaltType;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
import fr.xephi.authme.util.RandomStringUtils;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
/**
|
||||
* Hashing algorithm for SMF forums.
|
||||
* <p>
|
||||
@ -32,7 +34,7 @@ public class Smf implements EncryptionMethod {
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
return computeHash(password, null, name).equals(hashedPassword.getHash());
|
||||
return isEqual(hashedPassword.getHash(), computeHash(password, null, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -5,6 +5,8 @@ import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
/**
|
||||
* Common type for encryption methods which do not use any salt whatsoever.
|
||||
*/
|
||||
@ -26,7 +28,7 @@ public abstract class UnsaltedMethod implements EncryptionMethod {
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
return hashedPassword.getHash().equals(computeHash(password));
|
||||
return isEqual(hashedPassword.getHash(), computeHash(password));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -5,6 +5,8 @@ import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
/**
|
||||
* Common supertype of encryption methods that use a player's username
|
||||
* (or something based on it) as embedded salt.
|
||||
@ -23,7 +25,7 @@ public abstract class UsernameSaltMethod implements EncryptionMethod {
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
return hashedPassword.getHash().equals(computeHash(password, name).getHash());
|
||||
return isEqual(hashedPassword.getHash(), computeHash(password, name).getHash());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3,6 +3,7 @@ package fr.xephi.authme.security.crypts;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
import static fr.xephi.authme.security.crypts.BCryptService.hashpw;
|
||||
|
||||
@Recommendation(Usage.RECOMMENDED)
|
||||
@ -14,12 +15,12 @@ public class Wbb4 extends HexSaltedMethod {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) {
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
if (hashedPassword.getHash().length() != 60) {
|
||||
return false;
|
||||
}
|
||||
String salt = hashedPassword.getHash().substring(0, 29);
|
||||
return computeHash(password, salt, null).equals(hashedPassword.getHash());
|
||||
return isEqual(hashedPassword.getHash(), computeHash(password, salt, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -12,6 +12,8 @@ import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
@Recommendation(Usage.ACCEPTABLE)
|
||||
@HasSalt(value = SaltType.TEXT, length = 9)
|
||||
// Note ljacqu 20151228: Wordpress is actually a salted algorithm but salt generation is handled internally
|
||||
@ -115,7 +117,7 @@ public class Wordpress extends UnsaltedMethod {
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
String hash = hashedPassword.getHash();
|
||||
String comparedHash = crypt(password, hash);
|
||||
return comparedHash.equals(hash);
|
||||
return isEqual(hash, comparedHash);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package fr.xephi.authme.security.crypts;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.isEqual;
|
||||
|
||||
@Recommendation(Usage.RECOMMENDED)
|
||||
public class XAuth extends HexSaltedMethod {
|
||||
|
||||
@ -23,14 +25,14 @@ public class XAuth extends HexSaltedMethod {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) {
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
|
||||
String hash = hashedPassword.getHash();
|
||||
int saltPos = password.length() >= hash.length() ? hash.length() - 1 : password.length();
|
||||
if (saltPos + 12 > hash.length()) {
|
||||
return false;
|
||||
}
|
||||
String salt = hash.substring(saltPos, saltPos + 12);
|
||||
return hash.equals(computeHash(password, salt, null));
|
||||
return isEqual(hash, computeHash(password, salt, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2,7 +2,7 @@ package fr.xephi.authme.security.crypts;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.security.HashUtils;
|
||||
import fr.xephi.authme.util.StringUtils;
|
||||
import fr.xephi.authme.util.ExceptionUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@ -32,7 +32,7 @@ public class XfBCrypt implements EncryptionMethod {
|
||||
try {
|
||||
return HashUtils.isValidBcryptHash(hash.getHash()) && BCryptService.checkpw(password, hash.getHash());
|
||||
} catch (IllegalArgumentException e) {
|
||||
ConsoleLogger.warning("XfBCrypt checkpw() returned " + StringUtils.formatException(e));
|
||||
ConsoleLogger.warning("XfBCrypt checkpw() returned " + ExceptionUtils.formatException(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -0,0 +1,69 @@
|
||||
package fr.xephi.authme.security.totp;
|
||||
|
||||
import fr.xephi.authme.initialization.HasCleanup;
|
||||
import fr.xephi.authme.security.totp.TotpAuthenticator.TotpGenerationResult;
|
||||
import fr.xephi.authme.util.expiring.ExpiringMap;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Handles the generation of new TOTP secrets for players.
|
||||
*/
|
||||
public class GenerateTotpService implements HasCleanup {
|
||||
|
||||
private static final int NEW_TOTP_KEY_EXPIRATION_MINUTES = 5;
|
||||
|
||||
private final ExpiringMap<String, TotpGenerationResult> totpKeys;
|
||||
|
||||
@Inject
|
||||
private TotpAuthenticator totpAuthenticator;
|
||||
|
||||
GenerateTotpService() {
|
||||
this.totpKeys = new ExpiringMap<>(NEW_TOTP_KEY_EXPIRATION_MINUTES, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new TOTP key and returns the corresponding QR code.
|
||||
*
|
||||
* @param player the player to save the TOTP key for
|
||||
* @return TOTP generation result
|
||||
*/
|
||||
public TotpGenerationResult generateTotpKey(Player player) {
|
||||
TotpGenerationResult credentials = totpAuthenticator.generateTotpKey(player);
|
||||
totpKeys.put(player.getName().toLowerCase(), credentials);
|
||||
return credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the generated TOTP secret of a player, if available and not yet expired.
|
||||
*
|
||||
* @param player the player to retrieve the TOTP key for
|
||||
* @return TOTP generation result
|
||||
*/
|
||||
public TotpGenerationResult getGeneratedTotpKey(Player player) {
|
||||
return totpKeys.get(player.getName().toLowerCase());
|
||||
}
|
||||
|
||||
public void removeGenerateTotpKey(Player player) {
|
||||
totpKeys.remove(player.getName().toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given totp code is correct for the generated TOTP key of the player.
|
||||
*
|
||||
* @param player the player to verify the code for
|
||||
* @param totpCode the totp code to verify with the generated secret
|
||||
* @return true if the input code is correct, false if the code is invalid or no unexpired totp key is available
|
||||
*/
|
||||
public boolean isTotpCodeCorrectForGeneratedTotpKey(Player player, String totpCode) {
|
||||
TotpGenerationResult totpDetails = totpKeys.get(player.getName().toLowerCase());
|
||||
return totpDetails != null && totpAuthenticator.checkCode(totpDetails.getTotpKey(), totpCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performCleanup() {
|
||||
totpKeys.removeExpiredEntries();
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package fr.xephi.authme.security.totp;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.warrenstrange.googleauth.GoogleAuthenticator;
|
||||
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
|
||||
import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator;
|
||||
import com.warrenstrange.googleauth.IGoogleAuthenticator;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Provides TOTP functions (wrapping a third-party TOTP implementation).
|
||||
*/
|
||||
public class TotpAuthenticator {
|
||||
|
||||
private final IGoogleAuthenticator authenticator;
|
||||
private final BukkitService bukkitService;
|
||||
|
||||
@Inject
|
||||
TotpAuthenticator(BukkitService bukkitService) {
|
||||
this.authenticator = createGoogleAuthenticator();
|
||||
this.bukkitService = bukkitService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return new Google Authenticator instance
|
||||
*/
|
||||
protected IGoogleAuthenticator createGoogleAuthenticator() {
|
||||
return new GoogleAuthenticator();
|
||||
}
|
||||
|
||||
public boolean checkCode(PlayerAuth auth, String totpCode) {
|
||||
return checkCode(auth.getTotpKey(), totpCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given input code matches for the provided TOTP key.
|
||||
*
|
||||
* @param totpKey the key to check with
|
||||
* @param inputCode the input code to verify
|
||||
* @return true if code is valid, false otherwise
|
||||
*/
|
||||
public boolean checkCode(String totpKey, String inputCode) {
|
||||
Integer totpCode = Ints.tryParse(inputCode);
|
||||
return totpCode != null && authenticator.authorize(totpKey, totpCode);
|
||||
}
|
||||
|
||||
public TotpGenerationResult generateTotpKey(Player player) {
|
||||
GoogleAuthenticatorKey credentials = authenticator.createCredentials();
|
||||
String qrCodeUrl = GoogleAuthenticatorQRGenerator.getOtpAuthURL(
|
||||
bukkitService.getIp(), player.getName(), credentials);
|
||||
return new TotpGenerationResult(credentials.getKey(), qrCodeUrl);
|
||||
}
|
||||
|
||||
public static final class TotpGenerationResult {
|
||||
private final String totpKey;
|
||||
private final String authenticatorQrCodeUrl;
|
||||
|
||||
public TotpGenerationResult(String totpKey, String authenticatorQrCodeUrl) {
|
||||
this.totpKey = totpKey;
|
||||
this.authenticatorQrCodeUrl = authenticatorQrCodeUrl;
|
||||
}
|
||||
|
||||
public String getTotpKey() {
|
||||
return totpKey;
|
||||
}
|
||||
|
||||
public String getAuthenticatorQrCodeUrl() {
|
||||
return authenticatorQrCodeUrl;
|
||||
}
|
||||
}
|
||||
}
|
@ -393,4 +393,11 @@ public class BukkitService implements SettingsDependent {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the IP string that this server is bound to, otherwise empty string
|
||||
*/
|
||||
public String getIp() {
|
||||
return Bukkit.getServer().getIp();
|
||||
}
|
||||
}
|
||||
|
@ -21,8 +21,9 @@ import fr.xephi.authme.util.InternetProtocolUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
@ -33,6 +34,9 @@ import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
@ -55,6 +59,10 @@ public class GeoIpService {
|
||||
|
||||
private static final int UPDATE_INTERVAL_DAYS = 30;
|
||||
|
||||
// The server for MaxMind doesn't seem to understand RFC1123,
|
||||
// but every HTTP implementation have to support RFC 1023
|
||||
private static final String TIME_RFC_1023 = "EEE, dd-MMM-yy HH:mm:ss zzz";
|
||||
|
||||
private final Path dataFile;
|
||||
private final BukkitService bukkitService;
|
||||
|
||||
@ -98,13 +106,12 @@ public class GeoIpService {
|
||||
try {
|
||||
FileTime lastModifiedTime = Files.getLastModifiedTime(dataFile);
|
||||
if (Duration.between(lastModifiedTime.toInstant(), Instant.now()).toDays() <= UPDATE_INTERVAL_DAYS) {
|
||||
databaseReader = new Reader(dataFile.toFile(), FileMode.MEMORY, new CHMCache());
|
||||
ConsoleLogger.info(LICENSE);
|
||||
startReading();
|
||||
|
||||
// don't fire the update task - we are up to date
|
||||
return true;
|
||||
} else {
|
||||
ConsoleLogger.debug("GEO Ip database is older than " + UPDATE_INTERVAL_DAYS + " Days");
|
||||
ConsoleLogger.debug("GEO IP database is older than " + UPDATE_INTERVAL_DAYS + " Days");
|
||||
}
|
||||
} catch (IOException ioEx) {
|
||||
ConsoleLogger.logException("Failed to load GeoLiteAPI database", ioEx);
|
||||
@ -112,54 +119,101 @@ public class GeoIpService {
|
||||
}
|
||||
}
|
||||
|
||||
//set the downloading flag in order to fix race conditions outside
|
||||
downloading = true;
|
||||
|
||||
// File is outdated or doesn't exist - let's try to download the data file!
|
||||
startDownloadTask();
|
||||
// use bukkit's cached threads
|
||||
bukkitService.runTaskAsynchronously(this::updateDatabase);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a thread which will attempt to download new data from the GeoLite website.
|
||||
* Tries to update the database by downloading a new version from the website.
|
||||
*/
|
||||
private void startDownloadTask() {
|
||||
downloading = true;
|
||||
private void updateDatabase() {
|
||||
ConsoleLogger.info("Downloading GEO IP database, because the old database is older than "
|
||||
+ UPDATE_INTERVAL_DAYS + " days or doesn't exist");
|
||||
|
||||
// use bukkit's cached threads
|
||||
bukkitService.runTaskAsynchronously(() -> {
|
||||
ConsoleLogger.info("Downloading GEO IP database, because the old database is outdated or doesn't exist");
|
||||
|
||||
Path tempFile = null;
|
||||
try {
|
||||
// download database to temporarily location
|
||||
tempFile = Files.createTempFile(ARCHIVE_FILE, null);
|
||||
try (OutputStream out = Files.newOutputStream(tempFile)) {
|
||||
Resources.copy(new URL(ARCHIVE_URL), out);
|
||||
}
|
||||
|
||||
// MD5 checksum verification
|
||||
String targetChecksum = Resources.toString(new URL(CHECKSUM_URL), StandardCharsets.UTF_8);
|
||||
if (!verifyChecksum(Hashing.md5(), tempFile, targetChecksum)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// tar extract database and copy to target destination
|
||||
if (!extractDatabase(tempFile, dataFile)) {
|
||||
ConsoleLogger.warning("Cannot find database inside downloaded GEO IP file at " + tempFile);
|
||||
return;
|
||||
}
|
||||
|
||||
ConsoleLogger.info("Successfully downloaded new GEO IP database to " + dataFile);
|
||||
|
||||
//only set this value to false on success otherwise errors could lead to endless download triggers
|
||||
downloading = false;
|
||||
} catch (IOException ioEx) {
|
||||
ConsoleLogger.logException("Could not download GeoLiteAPI database", ioEx);
|
||||
} finally {
|
||||
// clean up
|
||||
if (tempFile != null) {
|
||||
FileUtils.delete(tempFile.toFile());
|
||||
}
|
||||
Path tempFile = null;
|
||||
try {
|
||||
// download database to temporarily location
|
||||
tempFile = Files.createTempFile(ARCHIVE_FILE, null);
|
||||
if (!downloadDatabaseArchive(tempFile)) {
|
||||
ConsoleLogger.info("There is no newer GEO IP database uploaded to MaxMind. Using the old one for now.");
|
||||
startReading();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// MD5 checksum verification
|
||||
String expectedChecksum = Resources.toString(new URL(CHECKSUM_URL), StandardCharsets.UTF_8);
|
||||
verifyChecksum(Hashing.md5(), tempFile, expectedChecksum);
|
||||
|
||||
// tar extract database and copy to target destination
|
||||
extractDatabase(tempFile, dataFile);
|
||||
|
||||
//only set this value to false on success otherwise errors could lead to endless download triggers
|
||||
ConsoleLogger.info("Successfully downloaded new GEO IP database to " + dataFile);
|
||||
startReading();
|
||||
} catch (IOException ioEx) {
|
||||
ConsoleLogger.logException("Could not download GeoLiteAPI database", ioEx);
|
||||
} finally {
|
||||
// clean up
|
||||
if (tempFile != null) {
|
||||
FileUtils.delete(tempFile.toFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startReading() throws IOException {
|
||||
databaseReader = new Reader(dataFile.toFile(), FileMode.MEMORY, new CHMCache());
|
||||
ConsoleLogger.info(LICENSE);
|
||||
|
||||
// clear downloading flag, because we now have working reader instance
|
||||
downloading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the archive to the destination file if it's newer than the locally version.
|
||||
*
|
||||
* @param lastModified modification timestamp of the already present file
|
||||
* @param destination save file
|
||||
* @return false if we already have the newest version, true if successful
|
||||
* @throws IOException if failed during downloading and writing to destination file
|
||||
*/
|
||||
private boolean downloadDatabaseArchive(Instant lastModified, Path destination) throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(ARCHIVE_URL).openConnection();
|
||||
if (lastModified != null) {
|
||||
// Only download if we actually need a newer version - this field is specified in GMT zone
|
||||
ZonedDateTime zonedTime = lastModified.atZone(ZoneId.of("GMT"));
|
||||
String timeFormat = DateTimeFormatter.ofPattern(TIME_RFC_1023).format(zonedTime);
|
||||
connection.addRequestProperty("If-Modified-Since", timeFormat);
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
//we already have the newest version
|
||||
connection.getInputStream().close();
|
||||
return false;
|
||||
}
|
||||
|
||||
Files.copy(connection.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the archive to the destination file if it's newer than the locally version.
|
||||
*
|
||||
* @param destination save file
|
||||
* @return false if we already have the newest version, true if successful
|
||||
* @throws IOException if failed during downloading and writing to destination file
|
||||
*/
|
||||
private boolean downloadDatabaseArchive(Path destination) throws IOException {
|
||||
Instant lastModified = null;
|
||||
if (Files.exists(dataFile)) {
|
||||
lastModified = Files.getLastModifiedTime(dataFile).toInstant();
|
||||
}
|
||||
|
||||
return downloadDatabaseArchive(lastModified, destination);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -168,19 +222,15 @@ public class GeoIpService {
|
||||
* @param function the checksum function like MD5, SHA256 used to generate the checksum from the file
|
||||
* @param file the file we want to calculate the checksum from
|
||||
* @param expectedChecksum the expected checksum
|
||||
* @return true if equal, false otherwise
|
||||
* @throws IOException on I/O error reading the file
|
||||
* @throws IOException on I/O error reading the file or the checksum verification failed
|
||||
*/
|
||||
private boolean verifyChecksum(HashFunction function, Path file, String expectedChecksum) throws IOException {
|
||||
private void verifyChecksum(HashFunction function, Path file, String expectedChecksum) throws IOException {
|
||||
HashCode actualHash = function.hashBytes(Files.readAllBytes(file));
|
||||
HashCode expectedHash = HashCode.fromString(expectedChecksum);
|
||||
if (Objects.equals(actualHash, expectedHash)) {
|
||||
return true;
|
||||
if (!Objects.equals(actualHash, expectedHash)) {
|
||||
throw new IOException("GEO IP Checksum verification failed. " +
|
||||
"Expected: " + expectedChecksum + "Actual:" + actualHash);
|
||||
}
|
||||
|
||||
ConsoleLogger.warning("GEO IP checksum verification failed");
|
||||
ConsoleLogger.warning("Expected: " + expectedHash + " Actual: " + actualHash);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -188,38 +238,37 @@ public class GeoIpService {
|
||||
*
|
||||
* @param tarInputFile gzipped tar input file where the database is
|
||||
* @param outputFile destination file for the database
|
||||
* @return true if the database was found, false otherwise
|
||||
* @throws IOException on I/O error reading the tar archive or writing the output
|
||||
* @throws IOException on I/O error reading the tar archive, or writing the output
|
||||
* @throws FileNotFoundException if the database cannot be found inside the archive
|
||||
*/
|
||||
private boolean extractDatabase(Path tarInputFile, Path outputFile) throws IOException {
|
||||
private void extractDatabase(Path tarInputFile, Path outputFile) throws FileNotFoundException, IOException {
|
||||
// .gz -> gzipped file
|
||||
try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(tarInputFile));
|
||||
TarInputStream tarIn = new TarInputStream(new GZIPInputStream(in))) {
|
||||
TarEntry entry;
|
||||
while ((entry = tarIn.getNextEntry()) != null) {
|
||||
if (!entry.isDirectory()) {
|
||||
// filename including folders (absolute path inside the archive)
|
||||
String filename = entry.getName();
|
||||
if (filename.endsWith(DATABASE_EXT)) {
|
||||
// found the database file
|
||||
Files.copy(tarIn, outputFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// update the last modification date to be same as in the archive
|
||||
Files.setLastModifiedTime(outputFile, FileTime.from(entry.getModTime().toInstant()));
|
||||
return true;
|
||||
}
|
||||
for (TarEntry entry = tarIn.getNextEntry(); entry != null; entry = tarIn.getNextEntry()) {
|
||||
// filename including folders (absolute path inside the archive)
|
||||
String filename = entry.getName();
|
||||
if (entry.isDirectory() || !filename.endsWith(DATABASE_EXT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// found the database file and copy file
|
||||
Files.copy(tarIn, outputFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// update the last modification date to be same as in the archive
|
||||
Files.setLastModifiedTime(outputFile, FileTime.from(entry.getModTime().toInstant()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
throw new FileNotFoundException("Cannot find database inside downloaded GEO IP file at " + tarInputFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return two-character ISO 3166-1 alpha code for the country or "--" if it cannot be fetched.
|
||||
*/
|
||||
public String getCountryCode(String ip) {
|
||||
return getCountry(ip).map(Country::getIsoCode).orElse("--");
|
||||
@ -229,7 +278,7 @@ public class GeoIpService {
|
||||
* Get the country name of the given IP address.
|
||||
*
|
||||
* @param ip textual IP address to lookup.
|
||||
* @return The name of the country.
|
||||
* @return The name of the country or "N/A" if it cannot be fetched.
|
||||
*/
|
||||
public String getCountryName(String ip) {
|
||||
return getCountry(ip).map(Country::getName).orElse("N/A");
|
||||
@ -255,7 +304,7 @@ public class GeoIpService {
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(ip);
|
||||
|
||||
//Reader.getCountry() can be null for unknown addresses
|
||||
// Reader.getCountry() can be null for unknown addresses
|
||||
return Optional.ofNullable(databaseReader.getCountry(address)).map(CountryResponse::getCountry);
|
||||
} catch (UnknownHostException e) {
|
||||
// Ignore invalid ip addresses
|
||||
|
@ -44,15 +44,17 @@ public class HelpTranslationGenerator {
|
||||
/**
|
||||
* Updates the help file to contain entries for all commands.
|
||||
*
|
||||
* @return the help file that has been updated
|
||||
* @throws IOException if the help file cannot be written to
|
||||
*/
|
||||
public void updateHelpFile() throws IOException {
|
||||
public File updateHelpFile() throws IOException {
|
||||
String languageCode = settings.getProperty(PluginSettings.MESSAGES_LANGUAGE);
|
||||
File helpFile = new File(dataFolder, "messages/help_" + languageCode + ".yml");
|
||||
Map<String, Object> helpEntries = generateHelpMessageEntries();
|
||||
|
||||
String helpEntriesYaml = exportToYaml(helpEntries);
|
||||
Files.write(helpFile.toPath(), helpEntriesYaml.getBytes(), StandardOpenOption.TRUNCATE_EXISTING);
|
||||
return helpFile;
|
||||
}
|
||||
|
||||
private static String exportToYaml(Map<String, Object> helpEntries) {
|
||||
|
@ -121,6 +121,16 @@ public class PasswordRecoveryService implements Reloadable, HasCleanup {
|
||||
commonService.send(player, MessageKey.RECOVERY_CHANGE_PASSWORD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a player from the list of successful recovers so that he can
|
||||
* no longer use the /email setpassword command.
|
||||
*
|
||||
* @param player The player to remove.
|
||||
*/
|
||||
public void removeFromSuccessfulRecovery(Player player) {
|
||||
successfulRecovers.remove(player.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a player is able to have emails sent.
|
||||
*
|
||||
@ -149,12 +159,7 @@ public class PasswordRecoveryService implements Reloadable, HasCleanup {
|
||||
String playerAddress = PlayerUtils.getPlayerIp(player);
|
||||
String storedAddress = successfulRecovers.get(name);
|
||||
|
||||
if (storedAddress == null || !playerAddress.equals(storedAddress)) {
|
||||
messages.send(player, MessageKey.CHANGE_PASSWORD_EXPIRED);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return storedAddress != null && playerAddress.equals(storedAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,5 +1,6 @@
|
||||
package fr.xephi.authme.service;
|
||||
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.data.auth.PlayerAuth;
|
||||
import fr.xephi.authme.data.auth.PlayerCache;
|
||||
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||
@ -63,12 +64,13 @@ public class TeleportationService implements Reloadable {
|
||||
public void teleportOnJoin(final Player player) {
|
||||
if (!settings.getProperty(RestrictionSettings.NO_TELEPORT)
|
||||
&& settings.getProperty(TELEPORT_UNAUTHED_TO_SPAWN)) {
|
||||
ConsoleLogger.debug("Teleport on join for player `{0}`", player.getName());
|
||||
teleportToSpawn(player, playerCache.isAuthenticated(player.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the player's custom on join location
|
||||
* Returns the player's custom on join location.
|
||||
*
|
||||
* @param player the player to process
|
||||
*
|
||||
@ -82,10 +84,11 @@ public class TeleportationService implements Reloadable {
|
||||
SpawnTeleportEvent event = new SpawnTeleportEvent(player, location,
|
||||
playerCache.isAuthenticated(player.getName()));
|
||||
bukkitService.callEvent(event);
|
||||
if(!isEventValid(event)) {
|
||||
if (!isEventValid(event)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ConsoleLogger.debug("Returning custom location for >1.9 join event for player `{0}`", player.getName());
|
||||
return location;
|
||||
}
|
||||
return null;
|
||||
@ -107,6 +110,7 @@ public class TeleportationService implements Reloadable {
|
||||
}
|
||||
|
||||
if (!player.hasPlayedBefore() || !dataSource.isAuthAvailable(player.getName())) {
|
||||
ConsoleLogger.debug("Attempting to teleport player `{0}` to first spawn", player.getName());
|
||||
performTeleportation(player, new FirstSpawnTeleportEvent(player, firstSpawn));
|
||||
}
|
||||
}
|
||||
@ -130,12 +134,15 @@ public class TeleportationService implements Reloadable {
|
||||
|
||||
// The world in LimboPlayer is from where the player comes, before any teleportation by AuthMe
|
||||
if (mustForceSpawnAfterLogin(worldName)) {
|
||||
ConsoleLogger.debug("Teleporting `{0}` to spawn because of 'force-spawn after login'", player.getName());
|
||||
teleportToSpawn(player, true);
|
||||
} else if (settings.getProperty(TELEPORT_UNAUTHED_TO_SPAWN)) {
|
||||
if (settings.getProperty(RestrictionSettings.SAVE_QUIT_LOCATION) && auth.getQuitLocY() != 0) {
|
||||
Location location = buildLocationFromAuth(player, auth);
|
||||
ConsoleLogger.debug("Teleporting `{0}` after login, based on the player auth", player.getName());
|
||||
teleportBackFromSpawn(player, location);
|
||||
} else if (limbo != null && limbo.getLocation() != null) {
|
||||
ConsoleLogger.debug("Teleporting `{0}` after login, based on the limbo player", player.getName());
|
||||
teleportBackFromSpawn(player, limbo.getLocation());
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import fr.xephi.authme.output.LogLevel;
|
||||
import fr.xephi.authme.process.register.RegisterSecondaryArgument;
|
||||
import fr.xephi.authme.process.register.RegistrationType;
|
||||
import fr.xephi.authme.security.HashAlgorithm;
|
||||
import fr.xephi.authme.settings.properties.DatabaseSettings;
|
||||
import fr.xephi.authme.settings.properties.PluginSettings;
|
||||
import fr.xephi.authme.settings.properties.RegistrationSettings;
|
||||
import fr.xephi.authme.settings.properties.SecuritySettings;
|
||||
@ -74,6 +75,7 @@ public class SettingsMigrationService extends PlainMigrationService {
|
||||
| convertToRegistrationType(resource)
|
||||
| mergeAndMovePermissionGroupSettings(resource)
|
||||
| moveDeprecatedHashAlgorithmIntoLegacySection(resource)
|
||||
| moveSaltColumnConfigWithOtherColumnConfigs(resource)
|
||||
|| hasDeprecatedProperties(resource);
|
||||
}
|
||||
|
||||
@ -313,6 +315,18 @@ public class SettingsMigrationService extends PlainMigrationService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the property for the password salt column name to the same path as all other column name properties.
|
||||
*
|
||||
* @param resource The property resource
|
||||
* @return True if the configuration has changed, false otherwise
|
||||
*/
|
||||
private static boolean moveSaltColumnConfigWithOtherColumnConfigs(PropertyResource resource) {
|
||||
Property<String> oldProperty = newProperty("ExternalBoardOptions.mySQLColumnSalt",
|
||||
DatabaseSettings.MYSQL_COL_SALT.getDefaultValue());
|
||||
return moveProperty(oldProperty, DatabaseSettings.MYSQL_COL_SALT, resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the old config to run a command when alt accounts are detected and sets them to this instance
|
||||
* for further processing.
|
||||
|
@ -198,9 +198,11 @@ public class SpawnLoader implements Reloadable {
|
||||
// ignore
|
||||
}
|
||||
if (spawnLoc != null) {
|
||||
ConsoleLogger.debug("Spawn location determined as `{0}` for world `{1}`", spawnLoc, world.getName());
|
||||
return spawnLoc;
|
||||
}
|
||||
}
|
||||
ConsoleLogger.debug("Fall back to default world spawn location. World: `{0}`", world.getName());
|
||||
return world.getSpawnLocation(); // return default location
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ public final class DatabaseSettings implements SettingsHolder {
|
||||
|
||||
@Comment("Column for storing players passwords salts")
|
||||
public static final Property<String> MYSQL_COL_SALT =
|
||||
newProperty("ExternalBoardOptions.mySQLColumnSalt", "");
|
||||
newProperty("DataSource.mySQLColumnSalt", "");
|
||||
|
||||
@Comment("Column for storing players emails")
|
||||
public static final Property<String> MYSQL_COL_EMAIL =
|
||||
@ -79,6 +79,10 @@ public final class DatabaseSettings implements SettingsHolder {
|
||||
public static final Property<String> MYSQL_COL_HASSESSION =
|
||||
newProperty("DataSource.mySQLColumnHasSession", "hasSession");
|
||||
|
||||
@Comment("Column for storing a player's TOTP key (for two-factor authentication)")
|
||||
public static final Property<String> MYSQL_COL_TOTP_KEY =
|
||||
newProperty("DataSource.mySQLtotpKey", "totp");
|
||||
|
||||
@Comment("Column for storing the player's last IP")
|
||||
public static final Property<String> MYSQL_COL_LAST_IP =
|
||||
newProperty("DataSource.mySQLColumnIp", "ip");
|
||||
|
@ -26,7 +26,7 @@ public final class RestrictionSettings implements SettingsHolder {
|
||||
@Comment("Allowed commands for unauthenticated players")
|
||||
public static final Property<List<String>> ALLOW_COMMANDS =
|
||||
newLowercaseListProperty("settings.restrictions.allowCommands",
|
||||
"/login", "/register", "/l", "/reg", "/email", "/captcha");
|
||||
"/login", "/register", "/l", "/reg", "/email", "/captcha", "/2fa", "/totp");
|
||||
|
||||
@Comment({
|
||||
"Max number of allowed registrations per IP",
|
||||
|
@ -49,7 +49,7 @@ public class PurgeExecutor {
|
||||
* players and names.
|
||||
*
|
||||
* @param players the players to purge
|
||||
* @param names names to purge
|
||||
* @param names names to purge
|
||||
*/
|
||||
public void executePurge(Collection<OfflinePlayer> players, Collection<String> names) {
|
||||
// Purge other data
|
||||
@ -212,15 +212,13 @@ public class PurgeExecutor {
|
||||
}
|
||||
|
||||
for (OfflinePlayer offlinePlayer : cleared) {
|
||||
try {
|
||||
permissionsManager.loadUserData(offlinePlayer.getUniqueId());
|
||||
} catch (NoSuchMethodError e) {
|
||||
permissionsManager.loadUserData(offlinePlayer.getName());
|
||||
if (!permissionsManager.loadUserData(offlinePlayer)) {
|
||||
ConsoleLogger.warning("Unable to purge the permissions of user " + offlinePlayer + "!");
|
||||
continue;
|
||||
}
|
||||
permissionsManager.removeAllGroups(offlinePlayer);
|
||||
}
|
||||
|
||||
ConsoleLogger.info("AutoPurge: Removed permissions from " + cleared.size() + " player(s).");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package fr.xephi.authme.task.purge;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.permission.PermissionsManager;
|
||||
import fr.xephi.authme.permission.PlayerStatePermission;
|
||||
import fr.xephi.authme.permission.handlers.PermissionLoadUserException;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
@ -73,10 +74,9 @@ class PurgeTask extends BukkitRunnable {
|
||||
|
||||
OfflinePlayer offlinePlayer = offlinePlayers[nextPosition];
|
||||
if (offlinePlayer.getName() != null && toPurge.remove(offlinePlayer.getName().toLowerCase())) {
|
||||
try {
|
||||
permissionsManager.loadUserData(offlinePlayer.getUniqueId());
|
||||
} catch (NoSuchMethodError e) {
|
||||
permissionsManager.loadUserData(offlinePlayer.getName());
|
||||
if(!permissionsManager.loadUserData(offlinePlayer)) {
|
||||
ConsoleLogger.warning("Unable to check if the user " + offlinePlayer.getName() + " can be purged!");
|
||||
continue;
|
||||
}
|
||||
if (!permissionsManager.hasPermissionOffline(offlinePlayer, PlayerStatePermission.BYPASS_PURGE)) {
|
||||
playerPortion.add(offlinePlayer);
|
||||
|
@ -33,4 +33,14 @@ public final class ExceptionUtils {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the information from a Throwable as string, retaining the type and its message.
|
||||
*
|
||||
* @param th the throwable to process
|
||||
* @return string with the type of the Throwable and its message, e.g. "[IOException]: Could not open stream"
|
||||
*/
|
||||
public static String formatException(Throwable th) {
|
||||
return "[" + th.getClass().getSimpleName() + "]: " + th.getMessage();
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,13 @@
|
||||
package fr.xephi.authme.util;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Utility class about the InternetProtocol
|
||||
*/
|
||||
public final class InternetProtocolUtils {
|
||||
|
||||
private static final Pattern LOCAL_ADDRESS_PATTERN =
|
||||
Pattern.compile("(^127\\.)|(^(0)?10\\.)|(^172\\.(0)?1[6-9]\\.)|(^172\\.(0)?2[0-9]\\.)"
|
||||
+ "|(^172\\.(0)?3[0-1]\\.)|(^169\\.254\\.)|(^192\\.168\\.)");
|
||||
|
||||
// Utility class
|
||||
private InternetProtocolUtils() {
|
||||
}
|
||||
@ -19,10 +16,57 @@ public final class InternetProtocolUtils {
|
||||
* Checks if the specified address is a private or loopback address
|
||||
*
|
||||
* @param address address to check
|
||||
*
|
||||
* @return true if the address is a local or loopback address, false otherwise
|
||||
* @return true if the address is a local (site and link) or loopback address, false otherwise
|
||||
*/
|
||||
public static boolean isLocalAddress(String address) {
|
||||
return LOCAL_ADDRESS_PATTERN.matcher(address).find();
|
||||
try {
|
||||
InetAddress inetAddress = InetAddress.getByName(address);
|
||||
|
||||
// Examples: 127.0.0.1, localhost or [::1]
|
||||
return isLoopbackAddress(address)
|
||||
// Example: 10.0.0.0, 172.16.0.0, 192.168.0.0, fec0::/10 (deprecated)
|
||||
// Ref: https://en.wikipedia.org/wiki/IP_address#Private_addresses
|
||||
|| inetAddress.isSiteLocalAddress()
|
||||
// Example: 169.254.0.0/16, fe80::/10
|
||||
// Ref: https://en.wikipedia.org/wiki/IP_address#Address_autoconfiguration
|
||||
|| inetAddress.isLinkLocalAddress()
|
||||
// non deprecated unique site-local that java doesn't check yet -> fc00::/7
|
||||
|| isIPv6UniqueSiteLocal(inetAddress);
|
||||
} catch (UnknownHostException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified address is a loopback address. This can be one of the following:
|
||||
* <ul>
|
||||
* <li>127.0.0.1</li>
|
||||
* <li>localhost</li>
|
||||
* <li>[::1]</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param address address to check
|
||||
* @return true if the address is a loopback one
|
||||
*/
|
||||
public static boolean isLoopbackAddress(String address) {
|
||||
try {
|
||||
InetAddress inetAddress = InetAddress.getByName(address);
|
||||
return inetAddress.isLoopbackAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isLoopbackAddress(InetAddress address) {
|
||||
return address.isLoopbackAddress();
|
||||
}
|
||||
|
||||
private static boolean isIPv6UniqueSiteLocal(InetAddress address) {
|
||||
// ref: https://en.wikipedia.org/wiki/Unique_local_address
|
||||
|
||||
// currently undefined but could be used in the near future fc00::/8
|
||||
return (address.getAddress()[0] & 0xFF) == 0xFC
|
||||
// in use for unique site-local fd00::/8
|
||||
|| (address.getAddress()[0] & 0xFF) == 0xFD;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user