Tyr Backbone Creation

# Changes:

## Patchwork
- Renamed FreedomExecutor to ExecutorProvider and moved the class to the provider package.
- Created an SQL Registry to prevent dependencies on Datura for SQL data. SQL is returned through an Optional, in the event that there is no SQL service registered.
- Created SQLResult, a generic ORM for ResultSets to avoid working directly with SQL data.

## Tyr
- Created Identity, which houses a username and related secret key.
- Created SQLEntry which stores the information from the Identity class into an SQL table called sessionData.
- Created TOTP, a simple static class that allows easy access to TimeBasedOneTimePasswordUtils class.
- Created OAuth2 which houses identities and performs the appropriate credential validations (incomplete)
This commit is contained in:
Paul Reilly 2023-09-09 18:57:15 -05:00
parent 85cc1f7ae0
commit 33731b611f
20 changed files with 889 additions and 172 deletions

View File

@ -32,7 +32,7 @@ import fns.datura.punishment.Locker;
import fns.datura.sql.MySQL; import fns.datura.sql.MySQL;
import fns.patchwork.base.Registration; import fns.patchwork.base.Registration;
import fns.patchwork.command.CommandHandler; import fns.patchwork.command.CommandHandler;
import fns.patchwork.service.SubscriptionProvider; import fns.patchwork.provider.SubscriptionProvider;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;

View File

@ -26,11 +26,11 @@ package fns.datura.sql;
import fns.patchwork.base.Patchwork; import fns.patchwork.base.Patchwork;
import fns.patchwork.base.Shortcuts; import fns.patchwork.base.Shortcuts;
import fns.patchwork.sql.SQL; import fns.patchwork.sql.SQL;
import fns.patchwork.sql.SQLResult;
import fns.patchwork.utils.container.Identity; import fns.patchwork.utils.container.Identity;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
@ -74,7 +74,7 @@ public class MySQL implements SQL
.append(password); .append(password);
} }
public CompletableFuture<ResultSet> getRow(final String table, final String column, final Identity identity) public CompletableFuture<SQLResult> getRow(final String table, final String column, final Identity identity)
{ {
return executeQuery("SELECT * FROM ? WHERE ? = ?", table, column, identity.getId()); return executeQuery("SELECT * FROM ? WHERE ? = ?", table, column, identity.getId());
} }
@ -93,7 +93,8 @@ public class MySQL implements SQL
statement.setObject(i + 1, args[i]); statement.setObject(i + 1, args[i]);
} }
return statement; return statement;
} catch (SQLException ex) }
catch (SQLException ex)
{ {
throw new CompletionException("Failed to prepare statement: " throw new CompletionException("Failed to prepare statement: "
+ query + "\n", ex); + query + "\n", ex);
@ -110,7 +111,8 @@ public class MySQL implements SQL
try try
{ {
return DriverManager.getConnection(url.toString()); return DriverManager.getConnection(url.toString());
} catch (SQLException ex) }
catch (SQLException ex)
{ {
throw new CompletionException("Failed to connect to the database: " throw new CompletionException("Failed to connect to the database: "
+ url.toString() + "\n", ex); + url.toString() + "\n", ex);
@ -121,15 +123,16 @@ public class MySQL implements SQL
} }
@Override @Override
public CompletableFuture<ResultSet> executeQuery(final String query, final Object... args) public CompletableFuture<SQLResult> executeQuery(final String query, final Object... args)
{ {
return prepareStatement(query, args) return prepareStatement(query, args)
.thenApplyAsync(statement -> .thenApplyAsync(statement ->
{ {
try try
{ {
return statement.executeQuery(); return new SQLResult(statement.executeQuery());
} catch (SQLException ex) }
catch (SQLException ex)
{ {
throw new CompletionException( throw new CompletionException(
"Failed to retrieve a result set from query: " "Failed to retrieve a result set from query: "
@ -149,7 +152,8 @@ public class MySQL implements SQL
try try
{ {
return statement.executeUpdate(); return statement.executeUpdate();
} catch (SQLException ex) }
catch (SQLException ex)
{ {
throw new CompletionException("Failed to execute update: " throw new CompletionException("Failed to execute update: "
+ query + "\n", ex); + query + "\n", ex);
@ -168,7 +172,8 @@ public class MySQL implements SQL
try try
{ {
return statement.execute(); return statement.execute();
} catch (SQLException ex) }
catch (SQLException ex)
{ {
throw new CompletionException("Failed to execute statement: " throw new CompletionException("Failed to execute statement: "
+ query + "\n", ex); + query + "\n", ex);
@ -201,23 +206,8 @@ public class MySQL implements SQL
final Identity identity, final Class<T> type) final Identity identity, final Class<T> type)
{ {
return executeQuery("SELECT ? FROM ? WHERE ? = ?", column, table, key, identity.getId()) return executeQuery("SELECT ? FROM ? WHERE ? = ?", column, table, key, identity.getId())
.thenApplyAsync(resultSet -> .thenApplyAsync(resultSet -> (resultSet.hasNext()) ? resultSet.autoCast(1, column, type) : null,
{ Shortcuts.provideModule(Patchwork.class)
try
{
if (resultSet.next())
{
return resultSet.getObject(column, type);
}
} catch (SQLException ex)
{
throw new CompletionException(
"Failed to retrieve column: " + column + " from table: " + table + " " +
"where primary key: " + key + " is equal to: " + identity.getId() + "\n",
ex);
}
return null;
}, Shortcuts.provideModule(Patchwork.class)
.getExecutor() .getExecutor()
.getAsync()); .getAsync());
} }

View File

@ -34,10 +34,8 @@ import fns.patchwork.sql.SQL;
import fns.patchwork.user.User; import fns.patchwork.user.User;
import fns.patchwork.user.UserData; import fns.patchwork.user.UserData;
import fns.patchwork.utils.logging.FNS4J; import fns.patchwork.utils.logging.FNS4J;
import java.sql.SQLException;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -94,9 +92,8 @@ public class SimpleUserData implements UserData
return sql.executeQuery("SELECT * FROM users WHERE UUID = ?", uuid) return sql.executeQuery("SELECT * FROM users WHERE UUID = ?", uuid)
.thenApplyAsync(result -> .thenApplyAsync(result ->
{ {
try
{ if (result.hasNext())
if (result.next())
{ {
final String g = result.getString("group"); final String g = result.getString("group");
@ -106,7 +103,8 @@ public class SimpleUserData implements UserData
final Player player = Bukkit.getPlayer(u); final Player player = Bukkit.getPlayer(u);
if (player == null) if (player == null)
throw new IllegalStateException("Player should be online but they are not!"); throw new IllegalStateException(
"Player should be online but they are not!");
final User user = new FreedomUser(player); final User user = new FreedomUser(player);
final Group group = Registration final Group group = Registration
@ -121,23 +119,20 @@ public class SimpleUserData implements UserData
return new SimpleUserData(u, username, user, group, playtime, return new SimpleUserData(u, username, user, group, playtime,
canInteract, balance, transactionsFrozen); canInteract, balance, transactionsFrozen);
} }
} catch (SQLException ex) else
{ {
final String sb = "An error occurred while trying to retrieve user data for" + final String sb = "An error occurred while trying to retrieve user data for" +
" UUID " + " UUID " +
uuid + uuid +
" from the database." + " from the database.";
"\nCaused by: " +
ExceptionUtils.getRootCauseMessage(ex) +
"\nStack trace: " +
ExceptionUtils.getStackTrace(ex);
FNS4J.getLogger("Datura") FNS4J.getLogger("Datura")
.error(sb); .error(sb);
} }
final Player player = Bukkit.getPlayer(UUID.fromString(uuid)); final Player player = Bukkit.getPlayer(UUID.fromString(uuid));
if (player == null) throw new IllegalStateException("Player should be online but they are not!"); if (player == null)
throw new IllegalStateException("Player should be online but they are not!");
return new SimpleUserData(player); return new SimpleUserData(player);
}, Shortcuts.provideModule(Patchwork.class) }, Shortcuts.provideModule(Patchwork.class)

View File

@ -27,7 +27,7 @@ import fns.fossil.cmd.CakeCommand;
import fns.fossil.trail.Trailer; import fns.fossil.trail.Trailer;
import fns.patchwork.base.Registration; import fns.patchwork.base.Registration;
import fns.patchwork.command.CommandHandler; import fns.patchwork.command.CommandHandler;
import fns.patchwork.service.SubscriptionProvider; import fns.patchwork.provider.SubscriptionProvider;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
public class Fossil extends JavaPlugin public class Fossil extends JavaPlugin

View File

@ -25,8 +25,8 @@ package fns.patchwork.base;
import fns.patchwork.display.adminchat.AdminChatDisplay; import fns.patchwork.display.adminchat.AdminChatDisplay;
import fns.patchwork.event.EventBus; import fns.patchwork.event.EventBus;
import fns.patchwork.service.FreedomExecutor; import fns.patchwork.provider.ExecutorProvider;
import fns.patchwork.service.SubscriptionProvider; import fns.patchwork.provider.SubscriptionProvider;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@ -40,9 +40,9 @@ public class Patchwork extends JavaPlugin
*/ */
private EventBus eventBus; private EventBus eventBus;
/** /**
* The {@link FreedomExecutor} for this plugin. * The {@link ExecutorProvider} for this plugin.
*/ */
private FreedomExecutor executor; private ExecutorProvider executor;
/** /**
* The {@link AdminChatDisplay} for this plugin. * The {@link AdminChatDisplay} for this plugin.
*/ */
@ -64,7 +64,7 @@ public class Patchwork extends JavaPlugin
public void onEnable() public void onEnable()
{ {
eventBus = new EventBus(this); eventBus = new EventBus(this);
executor = new FreedomExecutor(this); executor = new ExecutorProvider(this);
acdisplay = new AdminChatDisplay(this); acdisplay = new AdminChatDisplay(this);
@ -80,11 +80,11 @@ public class Patchwork extends JavaPlugin
} }
/** /**
* Gets the {@link FreedomExecutor} for this plugin. * Gets the {@link ExecutorProvider} for this plugin.
* *
* @return the {@link FreedomExecutor} * @return the {@link ExecutorProvider}
*/ */
public FreedomExecutor getExecutor() public ExecutorProvider getExecutor()
{ {
return executor; return executor;
} }

View File

@ -27,6 +27,7 @@ import fns.patchwork.registry.ConfigRegistry;
import fns.patchwork.registry.EventRegistry; import fns.patchwork.registry.EventRegistry;
import fns.patchwork.registry.GroupRegistry; import fns.patchwork.registry.GroupRegistry;
import fns.patchwork.registry.ModuleRegistry; import fns.patchwork.registry.ModuleRegistry;
import fns.patchwork.registry.SQLRegistry;
import fns.patchwork.registry.ServiceTaskRegistry; import fns.patchwork.registry.ServiceTaskRegistry;
import fns.patchwork.registry.UserRegistry; import fns.patchwork.registry.UserRegistry;
@ -62,6 +63,10 @@ public class Registration
* The {@link ConfigRegistry} * The {@link ConfigRegistry}
*/ */
private static final ConfigRegistry configRegistry = new ConfigRegistry(); private static final ConfigRegistry configRegistry = new ConfigRegistry();
/**
* The SQL Registry
*/
private static final SQLRegistry sqlRegistry = new SQLRegistry();
private Registration() private Registration()
{ {
@ -115,4 +120,12 @@ public class Registration
{ {
return configRegistry; return configRegistry;
} }
/**
* @return The {@link SQLRegistry}
*/
public static SQLRegistry getSQLRegistry()
{
return sqlRegistry;
}
} }

View File

@ -23,7 +23,10 @@
package fns.patchwork.base; package fns.patchwork.base;
import fns.patchwork.provider.ExecutorProvider;
import fns.patchwork.sql.SQL;
import fns.patchwork.user.User; import fns.patchwork.user.User;
import java.util.Optional;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@ -46,4 +49,14 @@ public final class Shortcuts
return Registration.getUserRegistry() return Registration.getUserRegistry()
.getUser(player); .getUser(player);
} }
public static ExecutorProvider getExecutors()
{
return provideModule(Patchwork.class).getExecutor();
}
public static Optional<SQL> getSQL()
{
return Registration.getSQLRegistry().getSQL();
}
} }

View File

@ -21,7 +21,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
package fns.patchwork.service; package fns.patchwork.provider;
import fns.patchwork.base.Patchwork; import fns.patchwork.base.Patchwork;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -34,7 +34,7 @@ import org.bukkit.plugin.java.JavaPlugin;
* This class is here for both convenience purposes, and also for the sake of providing easy access to executors for * This class is here for both convenience purposes, and also for the sake of providing easy access to executors for
* {@link CompletableFuture} invocations. * {@link CompletableFuture} invocations.
*/ */
public class FreedomExecutor public class ExecutorProvider
{ {
/** /**
* An executor which runs tasks synchronously. * An executor which runs tasks synchronously.
@ -46,9 +46,9 @@ public class FreedomExecutor
private final Executor asyncExecutor; private final Executor asyncExecutor;
/** /**
* Creates a new {@link FreedomExecutor} instance. * Creates a new {@link ExecutorProvider} instance.
*/ */
public FreedomExecutor(final Patchwork patchwork) public ExecutorProvider(final Patchwork patchwork)
{ {
syncExecutor = r -> Bukkit.getScheduler() syncExecutor = r -> Bukkit.getScheduler()
.runTask(patchwork, r); .runTask(patchwork, r);

View File

@ -21,8 +21,12 @@
* SOFTWARE. * SOFTWARE.
*/ */
package fns.patchwork.service; package fns.patchwork.provider;
import fns.patchwork.service.Service;
import fns.patchwork.service.ServiceSubscription;
import fns.patchwork.service.Task;
import fns.patchwork.service.TaskSubscription;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;

View File

@ -0,0 +1,44 @@
/*
* This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite
* Copyright (C) 2023 Simplex Development and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fns.patchwork.registry;
import fns.patchwork.sql.SQL;
import java.util.Optional;
public class SQLRegistry
{
private SQL sql;
public SQLRegistry() {
this.sql = null;
}
public Optional<SQL> getSQL() {
return (sql == null) ? Optional.empty() : Optional.of(sql);
}
public void setSQL(final SQL sql) {
this.sql = sql;
}
}

View File

@ -25,7 +25,7 @@ package fns.patchwork.registry;
import fns.patchwork.service.Service; import fns.patchwork.service.Service;
import fns.patchwork.service.ServiceSubscription; import fns.patchwork.service.ServiceSubscription;
import fns.patchwork.service.SubscriptionProvider; import fns.patchwork.provider.SubscriptionProvider;
import fns.patchwork.service.Task; import fns.patchwork.service.Task;
import fns.patchwork.service.TaskSubscription; import fns.patchwork.service.TaskSubscription;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -23,6 +23,8 @@
package fns.patchwork.service; package fns.patchwork.service;
import fns.patchwork.provider.SubscriptionProvider;
/** /**
* Represents a ticking service. Services may be asynchronous or synchronous, however there are some restrictions: * Represents a ticking service. Services may be asynchronous or synchronous, however there are some restrictions:
* <ul> * <ul>

View File

@ -31,7 +31,7 @@ public interface SQL
{ {
CompletableFuture<PreparedStatement> prepareStatement(final String query, final Object... args); CompletableFuture<PreparedStatement> prepareStatement(final String query, final Object... args);
CompletableFuture<ResultSet> executeQuery(final String query, final Object... args); CompletableFuture<SQLResult> executeQuery(final String query, final Object... args);
CompletableFuture<Integer> executeUpdate(final String query, final Object... args); CompletableFuture<Integer> executeUpdate(final String query, final Object... args);

View File

@ -0,0 +1,348 @@
/*
* This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite
* Copyright (C) 2023 Simplex Development and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fns.patchwork.sql;
import fns.patchwork.utils.container.Pair;
import fns.patchwork.utils.logging.FNS4J;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
public class SQLResult
{
private final Map<Integer, Map<String, Object>> resultMap = new HashMap<>();
/**
* This constructor will create a new SQLResult object from the specified ResultSet.
* This will iterate through all rows and columns of the ResultSet and store them in a Map.
* The Map will contain keys of integers representing the row number, and values of Maps
* containing the column names and their values.
*
* @param resultSet The ResultSet to create the SQLResult object from.
*/
public SQLResult(final ResultSet resultSet)
{
try
{
final ResultSetMetaData metaData = resultSet.getMetaData();
final int columnCount = metaData.getColumnCount();
int rowIndex = 0;
while (resultSet.next())
{
rowIndex++;
final Map<String, Object> rowMap = new HashMap<>();
for (int columnIndex = 1; columnIndex <= columnCount; columnIndex++)
{
String columnName = metaData.getColumnName(columnIndex);
Object columnValue = resultSet.getObject(columnIndex);
rowMap.put(columnName, columnValue);
}
resultMap.put(rowIndex, rowMap);
}
}
catch (SQLException ex)
{
FNS4J.getLogger("Tyr").error(ex.getMessage());
}
}
/**
* This method will return a map of all rows and their columns and values.
*
* @return A Map containing all rows and their columns and values.
*/
public Map<Integer, Map<String, Object>> getResultMap()
{
return resultMap;
}
/**
* This method will return a map of all columns and their values from the specified row.
*
* @param rowIndex The row index to get the column names from.
* @return A Map containing all column names and their values from the specified row.
*/
public Map<String, Object> getRow(final int rowIndex)
{
return resultMap.get(rowIndex);
}
/**
* This method will return the value from the specified row and column.
*
* @param rowIndex The row index to get the column name from.
* @param columnName The column name to get the value from.
* @return The value from the specified row and column.
*/
public Object getValue(final int rowIndex, final String columnName)
{
return resultMap.get(rowIndex).get(columnName);
}
/**
* This method will return the first value from the first row of the result set.
*
* @return A Pair containing the column name and the stored value.
*/
public Pair<String, Object> getFirst()
{
return new Pair<>(resultMap.get(1).entrySet().iterator().next().getKey(),
resultMap.get(1).entrySet().iterator().next().getValue());
}
/**
* This method will return the first value from the specified row of the result set.
*
* @param rowIndex The row index to get the column name from.
* @return A Pair containing the column name and the stored value.
*/
public Pair<String, Object> getFirst(final int rowIndex)
{
return new Pair<>(resultMap.get(rowIndex).entrySet().iterator().next().getKey(),
resultMap.get(rowIndex).entrySet().iterator().next().getValue());
}
/**
* This method will return the last value from the first row of the result set.
*
* @return A Pair containing the column name and the stored value.
*/
public Pair<String, Object> getLast()
{
return new Pair<>(resultMap.get(1).entrySet().iterator().next().getKey(),
resultMap.get(1).entrySet().iterator().next().getValue());
}
/**
* This method will return the last value from the specified row of the result set.
*
* @param rowIndex The row index to get the column name from.
* @return A Pair containing the column name and the stored value.
*/
public Pair<String, Object> getLast(final int rowIndex)
{
return new Pair<>(resultMap.get(rowIndex).entrySet().iterator().next().getKey(),
resultMap.get(rowIndex).entrySet().iterator().next().getValue());
}
/**
* This method will attempt to retrieve the value from the specified row and column,
* and cast it to the specified class. This will throw a {@link ClassCastException} if the
* returned value is not an instance of the provided class.
*
* @param rowIndex The row index to get the column name from.
* @param columnName The column name to get the value from.
* @param clazz The class to cast the value to.
* @param <T> The expected type.
* @return The value from the specified row and column, cast to the specified class.
*/
public <T> T autoCast(final int rowIndex, final String columnName, final Class<T> clazz)
{
final Object value = resultMap.get(rowIndex).get(columnName);
if (!clazz.isInstance(value))
throw new ClassCastException("Cannot cast " + value.getClass().getName() + " to " + clazz.getName());
return clazz.cast(resultMap.get(rowIndex).get(columnName));
}
/**
* @param rowIndex The row index to get the column names from.
* @return A Set containing all column names from the specified row of the result set.
*/
public Set<String> getColumnNames(final int rowIndex)
{
return resultMap.get(rowIndex).keySet();
}
/**
* @return A Set containing all column names from the first row of the result set.
*/
public Set<String> getColumnNames()
{
return resultMap.get(1).keySet();
}
/**
* This method will apply the specified consumer to all rows of the result set.
*
* @param columnConsumer The consumer to apply to all rows of the result set.
*/
public void accept(final Consumer<Map<String, Object>> columnConsumer)
{
this.resultMap.forEach((integer, map) -> columnConsumer.accept(map));
}
/**
* Checks to see if the result set contains the specified row number.
* Best used in a for loop, using {@link #rowCount()} as the upper bound.
*
* @param rowIndex The row index to check.
* @return True if the result set contains the specified row number, false otherwise.
*/
public boolean hasNext(final int rowIndex)
{
return this.resultMap.containsKey(rowIndex + 1);
}
/**
* Checks to see if the result set has the first row.
* If row 1 doesn't exist, it's safe to say the result set is empty.
*
* @return True if the result set has row 1, false otherwise.
*/
public boolean hasNext()
{
return this.resultMap.containsKey(1);
}
/**
* @return The number of rows in the result set.
*/
public int rowCount()
{
return this.resultMap.size();
}
/**
* @param rowIndex The row index from which to count columns.
* @return The number of columns in the specified row.
*/
public int columnCount(final int rowIndex)
{
return this.resultMap.get(rowIndex).size();
}
/**
* Retrieves a String value from the specified row and column.
*
* @param rowIndex The row index to get the column name from.
* @param columnName The column name to get the value from.
* @return The String value from the specified row and column.
* @see #autoCast(int, String, Class)
*/
public String getString(final int rowIndex, final String columnName)
{
return autoCast(rowIndex, columnName, String.class);
}
/**
* This method will attempt to retrieve a String value from the specified column within the first row of the
* result set.
*
* @param columnName The column name to get the value from.
* @return The String value from the specified column within the first row of the result set.
* @see #getString(int, String)
*/
public String getString(final String columnName)
{
return getString(1, columnName);
}
/**
* Retrieves an Integer value from the specified row and column.
*
* @param rowIndex The row index to get the column name from.
* @param columnName The column name to get the value from.
* @return The Integer value from the specified row and column.
* @see #autoCast(int, String, Class)
*/
public int getInteger(final int rowIndex, final String columnName)
{
return autoCast(rowIndex, columnName, Integer.class);
}
/**
* This method will attempt to retrieve an Integer value from the specified column within the first row of the
* result set.
*
* @param columnName The column name to get the value from.
* @return The Integer value from the specified column within the first row of the result set.
* @see #getInteger(int, String)
*/
public int getInteger(final String columnName)
{
return getInteger(1, columnName);
}
/**
* Retrieves a Long value from the specified row and column.
*
* @param rowIndex The row index to get the column name from.
* @param columnName The column name to get the value from.
* @return The Long value from the specified row and column.
* @see #autoCast(int, String, Class)
*/
public long getLong(final int rowIndex, final String columnName)
{
return autoCast(rowIndex, columnName, Long.class);
}
/**
* This method will attempt to retrieve a Long value from the specified column within the first row of the
* result set.
*
* @param columnName The column name to get the value from.
* @return The Long value from the specified column within the first row of the result set.
* @see #getLong(int, String)
*/
public long getLong(final String columnName)
{
return getLong(1, columnName);
}
/**
* Retrieves a Double value from the specified row and column.
*
* @param rowIndex The row index to get the column name from.
* @param columnName The column name to get the value from.
* @return The Double value from the specified row and column.
* @see #autoCast(int, String, Class)
*/
public boolean getBoolean(final int rowIndex, final String columnName)
{
return autoCast(rowIndex, columnName, Boolean.class);
}
/**
* This method will attempt to retrieve a Boolean value from the specified column within the first row of the
* result set.
*
* @param columnName The column name to get the value from.
* @return The Boolean value from the specified column within the first row of the result set.
* @see #getBoolean(int, String)
*/
public boolean getBoolean(final String columnName)
{
return getBoolean(1, columnName);
}
}

View File

@ -16,11 +16,10 @@ bukkit {
} }
dependencies { dependencies {
compileOnly project(":Patchwork") compileOnly project(path: ":Patchwork")
compileOnly project(":Datura") compileOnly project(path: ":Datura")
library 'com.hierynomus:sshj:0.28.0' library 'com.j256.two-factor-auth:two-factor-auth:1.3'
library 'org.bouncycastle:bcprov-jdk18on:1.76'
testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.junit.jupiter:junit-jupiter'

View File

@ -0,0 +1,48 @@
/*
* This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite
* Copyright (C) 2023 Simplex Development and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fns.tyr;
import fns.datura.Datura;
import fns.patchwork.base.Shortcuts;
import fns.patchwork.sql.SQL;
import fns.patchwork.utils.logging.FNS4J;
public class Tyr
{
public void onEnable()
{
final SQL sql = Shortcuts.provideModule(Datura.class).getSQL();
sql.createTable("sessionData",
"user VARCHAR(16) NOT NULL PRIMARY KEY, secretKey VARCHAR(64) NOT NULL;")
.whenCompleteAsync((result, throwable) ->
{
if (throwable != null)
FNS4J.getLogger("Tyr")
.error(throwable.getMessage());
}, Shortcuts.getExecutors()
.getAsync());
}
}

View File

@ -0,0 +1,93 @@
/*
* This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite
* Copyright (C) 2023 Simplex Development and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fns.tyr.data;
import fns.patchwork.base.Shortcuts;
import fns.patchwork.utils.logging.FNS4J;
import fns.tyr.oauth.Identity;
import java.sql.SQLException;
public class SQLEntry
{
private final Identity identity;
public SQLEntry(final Identity identity)
{
this.identity = identity;
}
public static SQLEntry load(final String username)
{
return Shortcuts.getSQL()
.map(c ->
c.executeQuery("SELECT * FROM sessionData WHERE user = ?;", username)
.thenApplyAsync(result ->
{
SQLEntry entry = null;
try
{
if (result.next())
{
final String user = result.getString("user");
final String secretKey = result.getString("secretKey");
final Identity i = new Identity(user, secretKey);
entry = new SQLEntry(i);
FNS4J.getLogger("Tyr")
.info("Loaded entry for " + username);
}
else
{
entry = new SQLEntry(Identity.of(username));
FNS4J.getLogger("Tyr")
.info("Created a new entry for " + username);
}
}
catch (SQLException ex)
{
FNS4J.getLogger("Tyr").error(ex.getMessage());
}
return entry;
}, Shortcuts.getExecutors()
.getAsync())
.join())
.orElseThrow(() -> new IllegalStateException("SQL is not initialized!"));
}
public void save()
{
Shortcuts.getSQL()
.orElseThrow(() -> new IllegalStateException("SQL is not available!"))
.executeUpdate("INSERT INTO sessionData (user, secretKey) VALUES (?, ?);",
this.identity.username(),
this.identity.secretKey())
.whenCompleteAsync((result, throwable) ->
{
if (throwable != null)
FNS4J.getLogger("Tyr").error(throwable.getMessage());
}, Shortcuts.getExecutors()
.getAsync());
}
}

View File

@ -0,0 +1,31 @@
/*
* This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite
* Copyright (C) 2023 Simplex Development and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fns.tyr.oauth;
public record Identity(String username, String secretKey)
{
public static Identity of(final String username) {
return new Identity(username, TOTP.createSecretKey());
}
}

View File

@ -0,0 +1,74 @@
/*
* This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite
* Copyright (C) 2023 Simplex Development and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fns.tyr.oauth;
import fns.patchwork.base.Shortcuts;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
public class OAuth2
{
private final Set<Identity> identitySet;
public OAuth2()
{
this.identitySet = new HashSet<>();
}
public void addIdentity(Identity identity)
{
this.identitySet.add(identity);
}
public void removeIdentity(Identity identity)
{
this.identitySet.remove(identity);
}
public Optional<Identity> getIdentity(final String username)
{
return this.identitySet.stream()
.filter(identity -> identity.username().equals(username))
.findFirst();
}
public void loadAll()
{
Shortcuts.getSQL()
.ifPresent(sql -> sql.executeQuery("SELECT * FROM sessionData;")
.thenAcceptAsync(result ->
{
for (int i = 1; i < result.rowCount(); i++)
{
final String username = result.getString(i,
"user");
final String secretKey = result.getString(i,
"secretKey");
this.addIdentity(
new Identity(username, secretKey));
}
}));
}
}

View File

@ -0,0 +1,63 @@
/*
* This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite
* Copyright (C) 2023 Simplex Development and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fns.tyr.oauth;
import com.j256.twofactorauth.TimeBasedOneTimePasswordUtil;
import fns.patchwork.utils.logging.FNS4J;
import java.security.GeneralSecurityException;
/**
* User-friendly version of TimeBasedOneTimePasswordUtil.
*/
public final class TOTP
{
private TOTP()
{
throw new AssertionError("This class cannot be instantiated.");
}
public static String createSecretKey()
{
return TimeBasedOneTimePasswordUtil.generateBase32Secret(32);
}
public static String createQRCode(final String username, final String secretKey)
{
return TimeBasedOneTimePasswordUtil.qrImageUrl(username, secretKey);
}
public static boolean verify(final String secretKey, final int userCode)
{
try
{
int vCode = TimeBasedOneTimePasswordUtil.generateCurrentNumber(secretKey);
return vCode == userCode;
}
catch (GeneralSecurityException ex)
{
FNS4J.getLogger("Tyr").error("Failed to verify TOTP code: " + ex.getMessage());
return false;
}
}
}