Merge branch 'release/5.2.5'

This commit is contained in:
Gravita 2021-11-05 22:26:43 +07:00
commit 0640fc6d60
25 changed files with 846 additions and 661 deletions

View file

@ -0,0 +1,94 @@
package pro.gravit.launchserver;
import com.google.gson.JsonElement;
import pro.gravit.launcher.Launcher;
import pro.gravit.launchserver.helper.HttpHelper;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.time.Duration;
public class HttpRequester {
private transient final HttpClient httpClient = HttpClient.newBuilder().build();
public HttpRequester() {
}
public static class SimpleErrorHandler<T> implements HttpHelper.HttpJsonErrorHandler<T, SimpleError> {
private final Type type;
private SimpleErrorHandler(Type type) {
this.type = type;
}
@Override
public HttpHelper.HttpOptional<T, SimpleError> applyJson(JsonElement response, int statusCode) {
if(statusCode < 200 || statusCode >= 300) {
return new HttpHelper.HttpOptional<>(null, Launcher.gsonManager.gson.fromJson(response, SimpleError.class), statusCode);
}
return new HttpHelper.HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode);
}
}
public <T> SimpleErrorHandler<T> makeEH(Class<T> clazz) {
return new SimpleErrorHandler<>(clazz);
}
public <T> HttpRequest get(String url, String token) {
try {
var requestBuilder = HttpRequest.newBuilder()
.method("GET", HttpRequest.BodyPublishers.noBody())
.uri(new URI(url))
.header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json")
.timeout(Duration.ofMillis(10000));
if(token != null) {
requestBuilder.header("Authorization", "Bearer ".concat(token));
}
return requestBuilder.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public <T> HttpRequest post(String url, T request, String token) {
try {
var requestBuilder = HttpRequest.newBuilder()
.method("POST", HttpRequest.BodyPublishers.ofString(Launcher.gsonManager.gson.toJson(request)))
.uri(new URI(url))
.header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json")
.timeout(Duration.ofMillis(10000));
if(token != null) {
requestBuilder.header("Authorization", "Bearer ".concat(token));
}
return requestBuilder.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public <T> HttpHelper.HttpOptional<T, SimpleError> send(HttpRequest request, Class<T> clazz) throws IOException {
return HttpHelper.send(httpClient, request, makeEH(clazz));
}
public static class SimpleError {
public String error;
public int code;
public SimpleError(String error) {
this.error = error;
}
@Override
public String toString() {
return "SimpleError{" +
"error='" + error + '\'' +
", code=" + code +
'}';
}
}
}

View file

@ -1,6 +1,10 @@
package pro.gravit.launchserver.auth; package pro.gravit.launchserver.auth;
import pro.gravit.launcher.events.request.AuthRequestEvent;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
public final class AuthException extends IOException { public final class AuthException extends IOException {
private static final long serialVersionUID = -2586107832847245863L; private static final long serialVersionUID = -2586107832847245863L;
@ -10,6 +14,24 @@ public AuthException(String message) {
super(message); super(message);
} }
public static AuthException need2FA() {
return new AuthException(AuthRequestEvent.TWO_FACTOR_NEED_ERROR_MESSAGE);
}
public static AuthException needMFA(List<Integer> factors) {
String message = AuthRequestEvent.ONE_FACTOR_NEED_ERROR_MESSAGE_PREFIX
.concat(factors.stream().map(String::valueOf).collect(Collectors.joining(".")));
return new AuthException(message);
}
public static AuthException wrongPassword() {
return new AuthException(AuthRequestEvent.WRONG_PASSWORD_ERROR_MESSAGE);
}
public static AuthException userNotFound() {
return new AuthException(AuthRequestEvent.USER_NOT_FOUND_ERROR_MESSAGE);
}
@Override @Override
public String toString() { public String toString() {
return getMessage(); return getMessage();

View file

@ -4,7 +4,6 @@
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.core.AuthCoreProvider; import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.core.AuthSocialProvider;
import pro.gravit.launchserver.auth.core.MySQLCoreProvider; import pro.gravit.launchserver.auth.core.MySQLCoreProvider;
import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.auth.texture.TextureProvider;
@ -17,7 +16,6 @@ public final class AuthProviderPair {
private transient final Logger logger = LogManager.getLogger(); private transient final Logger logger = LogManager.getLogger();
public boolean isDefault = true; public boolean isDefault = true;
public AuthCoreProvider core; public AuthCoreProvider core;
public AuthSocialProvider social;
public TextureProvider textureProvider; public TextureProvider textureProvider;
public Map<String, String> links; public Map<String, String> links;
public transient String name; public transient String name;
@ -25,22 +23,14 @@ public final class AuthProviderPair {
public String displayName; public String displayName;
private transient boolean warnOAuthShow = false; private transient boolean warnOAuthShow = false;
public AuthProviderPair() {
}
public AuthProviderPair(AuthCoreProvider core, TextureProvider textureProvider) { public AuthProviderPair(AuthCoreProvider core, TextureProvider textureProvider) {
this.core = core; this.core = core;
this.textureProvider = textureProvider; this.textureProvider = textureProvider;
} }
public AuthProviderPair(AuthCoreProvider core, AuthSocialProvider social) {
this.core = core;
this.social = social;
}
public AuthProviderPair(AuthCoreProvider core, AuthSocialProvider social, TextureProvider textureProvider) {
this.core = core;
this.social = social;
this.textureProvider = textureProvider;
}
public static Set<String> getFeatures(Class<?> clazz) { public static Set<String> getFeatures(Class<?> clazz) {
Set<String> list = new HashSet<>(); Set<String> list = new HashSet<>();
getFeatures(clazz, list); getFeatures(clazz, list);
@ -76,7 +66,6 @@ public static void getFeatures(Class<?> clazz, Set<String> list) {
public final <T> T isSupport(Class<T> clazz) { public final <T> T isSupport(Class<T> clazz) {
if (core == null) return null; if (core == null) return null;
T result = null; T result = null;
if (social != null) result = social.isSupport(clazz);
if (result == null) result = core.isSupport(clazz); if (result == null) result = core.isSupport(clazz);
return result; return result;
} }
@ -87,10 +76,6 @@ public final void init(LaunchServer srv, String name) {
core.init(srv); core.init(srv);
features = new HashSet<>(); features = new HashSet<>();
getFeatures(core.getClass(), features); getFeatures(core.getClass(), features);
if (social != null) {
social.init(srv, core);
getFeatures(social.getClass(), features);
}
} }
public final void link(LaunchServer srv) { public final void link(LaunchServer srv) {
@ -108,16 +93,9 @@ public final void link(LaunchServer srv) {
} }
public final void close() throws IOException { public final void close() throws IOException {
if (social != null) {
social.close();
}
core.close(); core.close();
if (textureProvider != null) { if (textureProvider != null) {
textureProvider.close(); textureProvider.close();
} }
} }
public final boolean isUseSocial() {
return core != null && social != null;
}
} }

View file

@ -45,7 +45,8 @@ public static void registerProviders() {
if (!registredProviders) { if (!registredProviders) {
providers.register("reject", RejectAuthCoreProvider.class); providers.register("reject", RejectAuthCoreProvider.class);
providers.register("mysql", MySQLCoreProvider.class); providers.register("mysql", MySQLCoreProvider.class);
providers.register("json", JsonCoreProvider.class); providers.register("memory", MemoryAuthCoreProvider.class);
providers.register("http", HttpAuthCoreProvider.class);
registredProviders = true; registredProviders = true;
} }
} }
@ -62,16 +63,22 @@ public User getUserByLogin(String login) {
public abstract AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context /* may be null */); public abstract AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context /* may be null */);
public abstract void verifyAuth(AuthResponse.AuthContext context) throws AuthException; public void verifyAuth(AuthResponse.AuthContext context) throws AuthException {
// None
}
public abstract PasswordVerifyReport verifyPassword(User user, AuthRequest.AuthPasswordInterface password); public abstract AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context /* may be null */, AuthRequest.AuthPasswordInterface password /* may be null */, boolean minecraftAccess) throws IOException;
public abstract AuthManager.AuthReport createOAuthSession(User user, AuthResponse.AuthContext context /* may be null */, PasswordVerifyReport report /* may be null */, boolean minecraftAccess) throws IOException; public AuthManager.AuthReport authorize(User user, AuthResponse.AuthContext context /* may be null */, AuthRequest.AuthPasswordInterface password /* may be null */, boolean minecraftAccess) throws IOException {
return authorize(user.getUsername(), context, password, minecraftAccess);
}
public abstract void init(LaunchServer server); public abstract void init(LaunchServer server);
// Auth Handler methods // Auth Handler methods
protected abstract boolean updateServerID(User user, String serverID) throws IOException; protected boolean updateServerID(User user, String serverID) throws IOException {
throw new UnsupportedOperationException();
}
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) { public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
return List.of(new AuthPasswordDetails()); return List.of(new AuthPasswordDetails());
@ -80,32 +87,28 @@ public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(
@Override @Override
public Map<String, Command> getCommands() { public Map<String, Command> getCommands() {
Map<String, Command> map = defaultCommandsMap(); Map<String, Command> map = defaultCommandsMap();
map.put("checkpassword", new SubCommand("[username] [json/plain password data]", "check password") { map.put("auth", new SubCommand("[login] (json/plain password data)", "Test auth") {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 2); verifyArgs(args, 1);
User user = getUserByUsername(args[0]); AuthRequest.AuthPasswordInterface password = null;
if (user == null) throw new CommandException("User not found"); if(args.length > 1) {
AuthRequest.AuthPasswordInterface password; if (args[1].startsWith("{")) {
if (args[1].startsWith("{")) { password = Launcher.gsonManager.gson.fromJson(args[1], AuthRequest.AuthPasswordInterface.class);
password = Launcher.gsonManager.gson.fromJson(args[1], AuthRequest.AuthPasswordInterface.class);
} else {
password = new AuthPlainPassword(args[1]);
}
PasswordVerifyReport report = verifyPassword(user, password);
if (report.success) {
logger.info("Password correct");
} else {
if (report.needMoreFactors) {
if (report.factors.size() == 1 && report.factors.get(0) == -1) {
logger.info("Password not correct: Required 2FA");
} else {
logger.info("Password not correct: Required more factors: {}", report.factors.toString());
}
} else { } else {
logger.info("Password incorrect"); password = new AuthPlainPassword(args[1]);
} }
} }
var report = authorize(args[0], null, password, false);
if (report.isUsingOAuth()) {
logger.info("OAuth: AccessToken: {} RefreshToken: {} MinecraftAccessToken: {}", report.oauthAccessToken(), report.oauthRefreshToken(), report.minecraftAccessToken());
if (report.session() != null) {
logger.info("UserSession: id {} expire {} user {}", report.session().getID(), report.session().getExpireIn(), report.session().getUser() == null ? "null" : "found");
logger.info(report.session().toString());
}
} else {
logger.info("Basic: MinecraftAccessToken: {}", report.minecraftAccessToken());
}
} }
}); });
map.put("getuserbyusername", new SubCommand("[username]", "get user by username") { map.put("getuserbyusername", new SubCommand("[username]", "get user by username") {
@ -132,32 +135,6 @@ public void invoke(String... args) throws Exception {
} }
} }
}); });
map.put("createsession", new SubCommand("[username] (true/false)", "create user session with/without minecraft access") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
User user = getUserByUsername(args[0]);
if (user == null) {
logger.info("User {} not found", args[0]);
return;
}
boolean minecraftAccess = args.length > 1 && Boolean.parseBoolean(args[1]);
AuthManager.AuthReport report = createOAuthSession(user, null, null, minecraftAccess);
if (report == null) {
logger.error("Method createOAuthSession return null");
return;
}
if (report.isUsingOAuth()) {
logger.info("OAuth: AccessToken: {} RefreshToken: {} MinecraftAccessToken: {}", report.oauthAccessToken(), report.oauthRefreshToken(), report.minecraftAccessToken());
if (report.session() != null) {
logger.info("UserSession: id {} expire {} user {}", report.session().getID(), report.session().getExpireIn(), report.session().getUser() == null ? "null" : "found");
logger.info(report.session().toString());
}
} else {
logger.info("Basic: MinecraftAccessToken: {}", report.minecraftAccessToken());
}
}
});
{ {
var instance = isSupport(AuthSupportGetAllUsers.class); var instance = isSupport(AuthSupportGetAllUsers.class);
if (instance != null) { if (instance != null) {
@ -355,6 +332,12 @@ public PasswordVerifyReport(boolean success) {
this.factors = List.of(); this.factors = List.of();
} }
public PasswordVerifyReport(AuthManager.AuthReport report) {
this.success = true;
this.needMoreFactors = false;
this.factors = List.of();
}
public PasswordVerifyReport(int nextFactor) { public PasswordVerifyReport(int nextFactor) {
this.success = false; this.success = false;
this.needMoreFactors = true; this.needMoreFactors = true;
@ -372,6 +355,10 @@ private PasswordVerifyReport(boolean success, boolean needMoreFactors, List<Inte
this.needMoreFactors = needMoreFactors; this.needMoreFactors = needMoreFactors;
this.factors = factors; this.factors = factors;
} }
public boolean isSuccess() {
return success;
}
} }
public static class OAuthAccessTokenExpired extends Exception { public static class OAuthAccessTokenExpired extends Exception {

View file

@ -1,61 +0,0 @@
package pro.gravit.launchserver.auth.core;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;
import java.util.List;
public abstract class AuthSocialProvider implements AutoCloseable {
public static final ProviderMap<AuthSocialProvider> providers = new ProviderMap<>("AuthSocialProvider");
private static final Logger logger = LogManager.getLogger();
private static boolean registredProviders = false;
public static void registerProviders() {
if (!registredProviders) {
registredProviders = true;
}
}
public abstract void init(LaunchServer server, AuthCoreProvider provider);
public abstract List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client);
public abstract SocialResult preAuth(AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password) throws AuthException;
@SuppressWarnings("unchecked")
public <T> T isSupport(Class<T> clazz) {
if (clazz.isAssignableFrom(getClass())) return (T) this;
return null;
}
@Override
public abstract void close() throws IOException;
public static class SocialResult {
public String login;
public AuthRequest.AuthPasswordInterface password;
public User user;
public SocialResult(String login, AuthRequest.AuthPasswordInterface password, User user) {
this.login = login;
this.password = password;
this.user = user;
}
public static SocialResult ofLoginAndPassword(String login, AuthRequest.AuthPasswordInterface password) {
return new SocialResult(login, password, null);
}
public static SocialResult ofUser(User user) {
return new SocialResult(null, null, user);
}
}
}

View file

@ -0,0 +1,333 @@
package pro.gravit.launchserver.auth.core;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.events.request.AuthRequestEvent;
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.profiles.Texture;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launchserver.HttpRequester;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures;
import pro.gravit.launchserver.helper.HttpHelper;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.CommonHelper;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
public class HttpAuthCoreProvider extends AuthCoreProvider {
private transient final Logger logger = LogManager.getLogger();
private transient HttpRequester requester;
public String bearerToken;
public String getUserByUsernameUrl;
public String getUserByLoginUrl;
public String getUserByUUIDUrl;
public String getUserByTokenUrl;
public String getAuthDetails;
public String refreshTokenUrl;
public String authorizeUrl;
public String joinServerUrl;
public String checkServerUrl;
public String updateServerIdUrl;
@Override
public User getUserByUsername(String username) {
try {
return requester.send(requester.get(CommonHelper.replace(getUserByUsernameUrl, "username", username), null), HttpUser.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public User getUserByLogin(String login) {
if(getUserByLoginUrl != null) {
try {
return requester.send(requester.get(CommonHelper.replace(getUserByLoginUrl, "login", login), null), HttpUser.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
return super.getUserByLogin(login);
}
@Override
public User getUserByUUID(UUID uuid) {
try {
return requester.send(requester.get(CommonHelper.replace(getUserByUUIDUrl, "uuid", uuid.toString()), null), HttpUser.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
if(getAuthDetails == null) {
return super.getDetails(client);
}
try {
var result = requester.send(requester.get(getAuthDetails, bearerToken), GetAuthDetailsResponse.class).getOrThrow();
return result.details;
} catch (IOException e) {
logger.error(e);
return super.getDetails(client);
}
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
if(getUserByTokenUrl == null) {
return null;
}
try {
var result = requester.send(requester.get(getUserByTokenUrl, accessToken), HttpUserSession.class);
if(!result.isSuccessful()) {
var error = result.error().error;
if(error.equals(AuthRequestEvent.OAUTH_TOKEN_EXPIRE)) {
throw new OAuthAccessTokenExpired();
}
return null;
}
return result.getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
if(refreshTokenUrl == null) {
return null;
}
try {
return requester.send(requester.post(refreshTokenUrl, new RefreshTokenRequest(refreshToken, context),
null), AuthManager.AuthReport.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
var result = requester.send(requester.post(authorizeUrl, new AuthorizeRequest(login, context, password, minecraftAccess),
bearerToken), AuthManager.AuthReport.class);
if(!result.isSuccessful()) {
var error = result.error().error;
if(error != null) {
throw new AuthException(error);
}
}
return result.getOrThrow();
}
@Override
protected boolean updateServerID(User user, String serverID) throws IOException {
var result = requester.send(requester.post(updateServerIdUrl, new UpdateServerIdRequest(user.getUsername(), user.getUUID(), serverID),
null), Void.class);
return result.isSuccessful();
}
@Override
public User checkServer(Client client, String username, String serverID) throws IOException {
return requester.send(requester.post(checkServerUrl, new CheckServerRequest(username, serverID), null), HttpUser.class).getOrThrow();
}
@Override
public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException {
var result = requester.send(requester.post(joinServerUrl, new JoinServerRequest(username, accessToken, serverID), null), Void.class);
return result.isSuccessful();
}
public static class UpdateServerIdRequest {
public String username;
public UUID uuid;
public String serverId;
public UpdateServerIdRequest(String username, UUID uuid, String serverId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
}
}
public static class CheckServerRequest {
public String username;
public String serverId;
public CheckServerRequest(String username, String serverId) {
this.username = username;
this.serverId = serverId;
}
}
public static class GetAuthDetailsResponse {
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details;
}
public static class JoinServerRequest {
public String username;
public String accessToken;
public String serverId;
public JoinServerRequest(String username, String accessToken, String serverId) {
this.username = username;
this.accessToken = accessToken;
this.serverId = serverId;
}
}
@Override
public void init(LaunchServer server) {
requester = new HttpRequester();
if(getUserByUsernameUrl == null) {
throw new IllegalArgumentException("'getUserByUsernameUrl' can't be null");
}
if(getUserByUUIDUrl == null) {
throw new IllegalArgumentException("'getUserByUUIDUrl' can't be null");
}
if(authorizeUrl == null) {
throw new IllegalArgumentException("'authorizeUrl' can't be null");
}
if((checkServerUrl == null && joinServerUrl == null) || updateServerIdUrl == null) {
throw new IllegalArgumentException("Please set 'checkServerUrl' and 'joinServerUrl' or 'updateServerIdUrl'");
}
}
@Override
public void close() throws IOException {
}
public static class AuthorizeRequest {
public String login;
public AuthResponse.AuthContext context;
public AuthRequest.AuthPasswordInterface password;
public boolean minecraftAccess;
public AuthorizeRequest() {
}
public AuthorizeRequest(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) {
this.login = login;
this.context = context;
this.password = password;
this.minecraftAccess = minecraftAccess;
}
}
public static class RefreshTokenRequest {
public String refreshToken;
public AuthResponse.AuthContext context;
public RefreshTokenRequest(String refreshToken, AuthResponse.AuthContext context) {
this.refreshToken = refreshToken;
this.context = context;
}
}
public static class HttpUser implements User, UserSupportTextures {
private String username;
private UUID uuid;
private String serverId;
private String accessToken;
private ClientPermissions permissions;
private Texture skin;
private Texture cloak;
public HttpUser() {
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Texture skin, Texture cloak) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.skin = skin;
this.cloak = cloak;
}
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public Texture getSkinTexture() {
return skin;
}
@Override
public Texture getCloakTexture() {
return cloak;
}
}
public static class HttpUserSession implements UserSession {
private String id;
private User user;
private long expireIn;
public HttpUserSession() {
}
public HttpUserSession(String id, User user, long expireIn) {
this.id = id;
this.user = user;
this.expireIn = expireIn;
}
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public long getExpireIn() {
return expireIn;
}
}
}

View file

@ -1,410 +0,0 @@
package pro.gravit.launchserver.auth.core;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.UUID;
public class JsonCoreProvider extends AuthCoreProvider {
private static transient final Logger logger = LogManager.getLogger();
public String getUserByUsernameUrl;
public String getUserByLoginUrl;
public String getUserByUUIDUrl;
public String getUserSessionByOAuthAccessTokenUrl;
public String getAuthDetailsUrl;
public String refreshAccessTokenUrl;
public String verifyPasswordUrl;
public String createOAuthSessionUrl;
public String updateServerIdUrl;
public String joinServerUrl;
public String checkServerUrl;
public String bearerToken;
public PasswordVerifier passwordVerifier;
private transient HttpClient client;
public static <T, R> R jsonRequest(T request, String url, String bearerToken, Class<R> clazz, HttpClient client) {
HttpRequest.BodyPublisher publisher;
if (request != null) {
publisher = HttpRequest.BodyPublishers.ofString(Launcher.gsonManager.gson.toJson(request));
} else {
publisher = HttpRequest.BodyPublishers.noBody();
}
try {
HttpRequest.Builder request1 = HttpRequest.newBuilder()
.method("POST", publisher)
.uri(new URI(url))
.header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json")
.timeout(Duration.ofMillis(10000));
if (bearerToken != null) {
request1.header("Authorization", "Bearer ".concat(bearerToken));
}
HttpResponse<InputStream> response = client.send(request1.build(), HttpResponse.BodyHandlers.ofInputStream());
int statusCode = response.statusCode();
if (200 > statusCode || statusCode > 300) {
if (statusCode >= 500) {
logger.error("JsonCoreProvider: {} return {}", url, statusCode);
} else if (statusCode >= 300 && statusCode <= 400) {
logger.error("JsonCoreProvider: {} return {}, try redirect to {}. Redirects not supported!", url, statusCode, response.headers().firstValue("Location").orElse("Unknown"));
} else if (statusCode == 403 || statusCode == 401) {
logger.error("JsonCoreProvider: {} return {}. Please set 'bearerToken'!", url, statusCode);
}
return null;
}
try (Reader reader = new InputStreamReader(response.body())) {
return Launcher.gsonManager.gson.fromJson(reader, clazz);
}
} catch (Exception e) {
return null;
}
}
@Override
public User getUserByUsername(String username) {
return jsonRequest(new JsonGetUserByUsername(username), getUserByUsernameUrl, JsonUser.class);
}
@Override
public User getUserByLogin(String login) {
if (getUserByLoginUrl != null) {
return jsonRequest(new JsonGetUserByUsername(login), getUserByLoginUrl, JsonUser.class);
}
return super.getUserByLogin(login);
}
@Override
public User getUserByUUID(UUID uuid) {
return jsonRequest(new JsonGetUserByUUID(uuid), getUserByUUIDUrl, JsonUser.class);
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
if (getUserSessionByOAuthAccessTokenUrl == null) {
return null;
}
JsonGetUserSessionByOAuthTokenResponse response = jsonRequest(new JsonGetUserSessionByAccessToken(accessToken), getUserSessionByOAuthAccessTokenUrl, JsonGetUserSessionByOAuthTokenResponse.class);
if (response == null) return null;
if (!response.expired) throw new OAuthAccessTokenExpired();
return response.session;
}
@Override
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
if (getAuthDetailsUrl != null) {
JsonGetDetailsResponse response = jsonRequest(new JsonGetDetails(), getAuthDetailsUrl, JsonGetDetailsResponse.class);
if (response == null) return super.getDetails(client);
return response.details;
}
return super.getDetails(client);
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
JsonAuthReportResponse response = jsonRequest(new JsonRefreshToken(refreshToken, context.ip), this.refreshAccessTokenUrl, JsonAuthReportResponse.class);
return response == null ? null : response.toAuthReport();
}
@Override
public void verifyAuth(AuthResponse.AuthContext context) throws AuthException {
}
@Override
public PasswordVerifyReport verifyPassword(User user, AuthRequest.AuthPasswordInterface password) {
JsonUser jsonUser = (JsonUser) user;
if (password instanceof AuthPlainPassword && jsonUser.password != null && passwordVerifier != null) {
if (passwordVerifier.check(jsonUser.password, ((AuthPlainPassword) password).password)) {
return PasswordVerifyReport.OK;
} else {
return PasswordVerifyReport.FAILED;
}
}
if (user == null) {
return jsonRequest(new JsonPasswordVerify(null, null, password), verifyPasswordUrl, PasswordVerifyReport.class);
}
return jsonRequest(new JsonPasswordVerify(user.getUsername(), user.getUUID(), password), verifyPasswordUrl, PasswordVerifyReport.class);
}
@Override
public AuthManager.AuthReport createOAuthSession(User user, AuthResponse.AuthContext context, PasswordVerifyReport report, boolean minecraftAccess) throws IOException {
JsonAuthReportResponse response = jsonRequest(new JsonCreateOAuthSession(user == null ? null : user.getUsername(), user == null ? null : user.getUUID(), minecraftAccess), createOAuthSessionUrl, JsonAuthReportResponse.class);
if (response == null) return null;
if (response.error != null) throw new AuthException(response.error);
JsonUser user1 = (JsonUser) user;
user1.accessToken = response.minecraftAccessToken;
return response.toAuthReport();
}
@Override
public void init(LaunchServer server) {
client = HttpClient.newBuilder().build();
}
@Override
public User checkServer(Client client, String username, String serverID) throws IOException {
if (checkServerUrl == null) {
return super.checkServer(client, username, serverID);
}
return jsonRequest(new JsonCheckServer(username, serverID), checkServerUrl, JsonUser.class);
}
@Override
public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException {
if (joinServerUrl == null) {
return super.joinServer(client, username, accessToken, serverID);
}
return jsonRequest(new JsonJoinServer(username, accessToken, serverID), joinServerUrl, JsonSuccessResponse.class).success;
}
@Override
protected boolean updateServerID(User user, String serverID) throws IOException {
JsonUser jsonUser = (JsonUser) user;
if (updateServerIdUrl == null) {
return false;
}
jsonUser.serverId = serverID;
JsonSuccessResponse successResponse = jsonRequest(new JsonUpdateServerId(user.getUsername(), user.getUUID(), serverID), updateServerIdUrl, JsonSuccessResponse.class);
if (successResponse == null) return false;
return successResponse.success;
}
@Override
public void close() throws IOException {
}
public <T, R> R jsonRequest(T request, String url, Class<R> clazz) {
return jsonRequest(request, url, bearerToken, clazz, client);
}
public static class JsonGetUserByUsername {
public String username;
public JsonGetUserByUsername(String username) {
this.username = username;
}
}
public static class JsonCheckServer {
public String username;
public String serverId;
public JsonCheckServer(String username, String serverId) {
this.username = username;
this.serverId = serverId;
}
}
public static class JsonJoinServer {
public String username;
public String accessToken;
public String serverId;
public JsonJoinServer(String username, String accessToken, String serverId) {
this.username = username;
this.accessToken = accessToken;
this.serverId = serverId;
}
}
public static class JsonGetUserByUUID {
public UUID uuid;
public JsonGetUserByUUID(UUID uuid) {
this.uuid = uuid;
}
}
public static class JsonGetUserSessionByAccessToken {
public String accessToken;
public JsonGetUserSessionByAccessToken(String accessToken) {
this.accessToken = accessToken;
}
}
public static class JsonRefreshToken {
public String refreshToken;
public String ip;
public JsonRefreshToken(String refreshToken, String ip) {
this.refreshToken = refreshToken;
this.ip = ip;
}
}
public static class JsonAuthReportResponse {
public String minecraftAccessToken;
public String oauthAccessToken;
public String oauthRefreshToken;
public long oauthExpire;
public JsonUserSession session;
public String error;
public AuthManager.AuthReport toAuthReport() {
return new AuthManager.AuthReport(minecraftAccessToken, oauthAccessToken, oauthRefreshToken, oauthExpire, session);
}
}
public static class JsonPasswordVerify {
public String username;
public UUID uuid;
public AuthRequest.AuthPasswordInterface password;
public JsonPasswordVerify(String username, UUID uuid, AuthRequest.AuthPasswordInterface password) {
this.username = username;
this.uuid = uuid;
this.password = password;
}
}
public static class JsonCreateOAuthSession {
public String username;
public UUID uuid;
public boolean minecraftAccess;
public JsonCreateOAuthSession(String username, UUID uuid, boolean minecraftAccess) {
this.username = username;
this.uuid = uuid;
this.minecraftAccess = minecraftAccess;
}
}
public static class JsonUpdateServerId {
public String username;
public UUID uuid;
public String serverId;
public JsonUpdateServerId(String username, UUID uuid, String serverId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
}
}
public static class JsonSuccessResponse {
public boolean success;
}
public static class JsonGetUserSessionByOAuthTokenResponse {
public boolean expired;
public JsonUserSession session;
public JsonGetUserSessionByOAuthTokenResponse() {
}
}
public static class JsonGetDetails {
}
public static class JsonGetDetailsResponse {
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details;
}
public static class JsonUser implements User {
private String username;
private UUID uuid;
private String serverId;
private String accessToken;
private ClientPermissions permissions;
private String password;
public JsonUser() {
}
public JsonUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, String password) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.password = password;
}
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public String toString() {
return "JsonUser{" +
"username='" + username + '\'' +
", uuid=" + uuid +
", permissions=" + permissions +
'}';
}
}
public static class JsonUserSession implements UserSession {
public String id;
public JsonUser user;
public long expireIn;
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public long getExpireIn() {
return expireIn;
}
@Override
public String toString() {
return "JsonUserSession{" +
"id='" + id + '\'' +
"user='" + (user == null ? null : user.getUsername()) + '\'' +
", expireIn=" + expireIn +
'}';
}
}
}

View file

@ -0,0 +1,212 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.details.AuthLoginOnlyDetails;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class MemoryAuthCoreProvider extends AuthCoreProvider {
private transient final List<MemoryUser> memory = new ArrayList<>(16);
@Override
public User getUserByUsername(String username) {
synchronized (memory) {
for(MemoryUser u : memory) {
if(u.username.equals(username)) {
return u;
}
}
var result = new MemoryUser(username);
memory.add(result);
return result;
}
}
@Override
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
return List.of(new AuthLoginOnlyDetails());
}
@Override
public User getUserByUUID(UUID uuid) {
synchronized (memory) {
for(MemoryUser u : memory) {
if(u.uuid.equals(uuid)) {
return u;
}
}
}
return null;
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
synchronized (memory) {
for(MemoryUser u : memory) {
if(u.accessToken.equals(accessToken)) {
return new MemoryUserSession(u);
}
}
}
return null;
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
return null;
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
if(login == null) {
throw AuthException.userNotFound();
}
MemoryUser user = null;
synchronized (memory) {
for(MemoryUser u : memory) {
if(u.username.equals(login)) {
user = u;
break;
}
}
if(user == null) {
user = new MemoryUser(login);
memory.add(user);
}
}
if(minecraftAccess) {
return AuthManager.AuthReport.ofOAuth(user.accessToken, null, 0, new MemoryUserSession(user));
} else {
return AuthManager.AuthReport.ofOAuthWithMinecraft(user.accessToken, user.accessToken, null, 0, new MemoryUserSession(user));
}
}
@Override
protected boolean updateServerID(User user, String serverID) throws IOException {
MemoryUser memoryUser = (MemoryUser) user;
memoryUser.serverId = serverID;
return true;
}
@Override
public User checkServer(Client client, String username, String serverID) throws IOException {
synchronized (memory) {
for(MemoryUser u : memory) {
if(u.username.equals(username)) {
return u;
}
}
var result = new MemoryUser(username);
memory.add(result);
return result;
}
}
@Override
public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException {
return true;
}
@Override
public void init(LaunchServer server) {
}
@Override
public void close() throws IOException {
}
public static class MemoryUser implements User {
private String username;
private UUID uuid;
private String serverId;
private String accessToken;
private ClientPermissions permissions;
public MemoryUser(String username) {
this.username = username;
this.uuid = makeUuidFromUsername(username);
this.accessToken = SecurityHelper.randomStringToken();
this.permissions = new ClientPermissions();
}
private static UUID makeUuidFromUsername(String username) {
return UUID.nameUUIDFromBytes(username.getBytes(StandardCharsets.UTF_8));
}
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MemoryUser that = (MemoryUser) o;
return uuid.equals(that.uuid);
}
@Override
public int hashCode() {
return Objects.hash(uuid);
}
}
public static class MemoryUserSession implements UserSession {
private String id;
private MemoryUser user;
private long expireIn;
public MemoryUserSession(MemoryUser user) {
this.id = SecurityHelper.randomStringToken();
this.user = user;
this.expireIn = 0;
}
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public long getExpireIn() {
return expireIn;
}
}
}

View file

@ -105,27 +105,27 @@ public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthRespon
} }
@Override @Override
public void verifyAuth(AuthResponse.AuthContext context) throws AuthException { public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
MySQLUser mySQLUser = (MySQLUser) getUserByLogin(login);
} if(mySQLUser == null) {
throw AuthException.wrongPassword();
@Override
public PasswordVerifyReport verifyPassword(User user, AuthRequest.AuthPasswordInterface password) {
if (passwordVerifier.check(((MySQLUser) user).password, ((AuthPlainPassword) password).password)) {
return PasswordVerifyReport.OK;
} else {
return PasswordVerifyReport.FAILED;
} }
} if(context != null) {
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
@Override if(plainPassword == null) {
public AuthManager.AuthReport createOAuthSession(User user, AuthResponse.AuthContext context, PasswordVerifyReport report, boolean minecraftAccess) throws IOException { throw AuthException.wrongPassword();
}
if(!passwordVerifier.check(mySQLUser.password, plainPassword.password)) {
throw AuthException.wrongPassword();
}
}
MySQLUserSession session = new MySQLUserSession(mySQLUser);
if (minecraftAccess) { if (minecraftAccess) {
String minecraftAccessToken = SecurityHelper.randomStringToken(); String minecraftAccessToken = SecurityHelper.randomStringToken();
updateAuth(user, minecraftAccessToken); updateAuth(mySQLUser, minecraftAccessToken);
return AuthManager.AuthReport.ofMinecraftAccessToken(minecraftAccessToken); return AuthManager.AuthReport.ofMinecraftAccessToken(minecraftAccessToken, session);
} else { } else {
return AuthManager.AuthReport.ofMinecraftAccessToken(null); return AuthManager.AuthReport.ofMinecraftAccessToken(null, session);
} }
} }
@ -333,13 +333,14 @@ public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwa
} }
@Override @Override
public void connectUserAndHardware(User user, UserHardware hardware) { public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
MySQLUser mySQLUser = (MySQLUser) user; MySQLUserSession mySQLUserSession = (MySQLUserSession) userSession;
MySQLUser mySQLUser = mySQLUserSession.user;
MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware; MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware;
if (mySQLUser.hwidId == mySQLUserHardware.id) return; if (mySQLUser.hwidId == mySQLUserHardware.id) return;
mySQLUser.hwidId = mySQLUserHardware.id; mySQLUser.hwidId = mySQLUserHardware.id;
try (Connection connection = mySQLHolder.getConnection()) { try (Connection connection = mySQLHolder.getConnection()) {
setUserHardwareId(connection, user.getUUID(), mySQLUserHardware.id); setUserHardwareId(connection, mySQLUser.getUUID(), mySQLUserHardware.id);
} catch (SQLException throwables) { } catch (SQLException throwables) {
logger.error("SQL Error", throwables); logger.error("SQL Error", throwables);
} }
@ -513,4 +514,29 @@ public String toString() {
'}'; '}';
} }
} }
public static class MySQLUserSession implements UserSession {
private final MySQLUser user;
private final String id;
public MySQLUserSession(MySQLUser user) {
this.user = user;
this.id = user.username;
}
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public long getExpireIn() {
return 0;
}
}
} }

View file

@ -36,13 +36,8 @@ public void verifyAuth(AuthResponse.AuthContext context) throws AuthException {
} }
@Override @Override
public PasswordVerifyReport verifyPassword(User user, AuthRequest.AuthPasswordInterface password) { public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
return PasswordVerifyReport.FAILED; throw new AuthException("Please configure AuthCoreProvider");
}
@Override
public AuthManager.AuthReport createOAuthSession(User user, AuthResponse.AuthContext context, PasswordVerifyReport report, boolean minecraftAccess) throws IOException {
return null;
} }
@Override @Override

View file

@ -2,6 +2,7 @@
import pro.gravit.launcher.request.secure.HardwareReportRequest; import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.auth.core.User; import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware; import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
import pro.gravit.launchserver.auth.protect.hwid.HWIDProvider; import pro.gravit.launchserver.auth.protect.hwid.HWIDProvider;
@ -18,7 +19,7 @@ public interface AuthSupportHardware extends AuthSupport {
UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo info, byte[] publicKey); UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo info, byte[] publicKey);
void connectUserAndHardware(User user, UserHardware hardware); void connectUserAndHardware(UserSession userSession, UserHardware hardware);
void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey); void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey);

View file

@ -5,4 +5,9 @@ public class AcceptPasswordVerifier extends PasswordVerifier {
public boolean check(String encryptedPassword, String password) { public boolean check(String encryptedPassword, String password) {
return true; return true;
} }
@Override
public String encrypt(String password) {
return "";
}
} }

View file

@ -1,17 +1,27 @@
package pro.gravit.launchserver.auth.password; package pro.gravit.launchserver.auth.password;
import pro.gravit.launchserver.auth.core.JsonCoreProvider; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.Launcher;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class JsonPasswordVerifier extends PasswordVerifier { public class JsonPasswordVerifier extends PasswordVerifier {
private static transient final Logger logger = LogManager.getLogger();
private transient final HttpClient client = HttpClient.newBuilder().build(); private transient final HttpClient client = HttpClient.newBuilder().build();
public String url; public String url;
public String bearerToken; public String bearerToken;
@Override @Override
public boolean check(String encryptedPassword, String password) { public boolean check(String encryptedPassword, String password) {
JsonPasswordResponse response = JsonCoreProvider.jsonRequest(new JsonPasswordRequest(encryptedPassword, password), url, bearerToken, JsonPasswordResponse.class, client); JsonPasswordResponse response = jsonRequest(new JsonPasswordRequest(encryptedPassword, password), url, bearerToken, JsonPasswordResponse.class, client);
if (response != null) { if (response != null) {
return response.success; return response.success;
} }
@ -31,4 +41,41 @@ public JsonPasswordRequest(String encryptedPassword, String password) {
public static class JsonPasswordResponse { public static class JsonPasswordResponse {
public boolean success; public boolean success;
} }
public static <T, R> R jsonRequest(T request, String url, String bearerToken, Class<R> clazz, HttpClient client) {
HttpRequest.BodyPublisher publisher;
if (request != null) {
publisher = HttpRequest.BodyPublishers.ofString(Launcher.gsonManager.gson.toJson(request));
} else {
publisher = HttpRequest.BodyPublishers.noBody();
}
try {
HttpRequest.Builder request1 = HttpRequest.newBuilder()
.method("POST", publisher)
.uri(new URI(url))
.header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json")
.timeout(Duration.ofMillis(10000));
if (bearerToken != null) {
request1.header("Authorization", "Bearer ".concat(bearerToken));
}
HttpResponse<InputStream> response = client.send(request1.build(), HttpResponse.BodyHandlers.ofInputStream());
int statusCode = response.statusCode();
if (200 > statusCode || statusCode > 300) {
if (statusCode >= 500) {
logger.error("JsonCoreProvider: {} return {}", url, statusCode);
} else if (statusCode >= 300 && statusCode <= 400) {
logger.error("JsonCoreProvider: {} return {}, try redirect to {}. Redirects not supported!", url, statusCode, response.headers().firstValue("Location").orElse("Unknown"));
} else if (statusCode == 403 || statusCode == 401) {
logger.error("JsonCoreProvider: {} return {}. Please set 'bearerToken'!", url, statusCode);
}
return null;
}
try (Reader reader = new InputStreamReader(response.body())) {
return Launcher.gsonManager.gson.fromJson(reader, clazz);
}
} catch (Exception e) {
return null;
}
}
} }

View file

@ -76,7 +76,7 @@ public void onHardwareReport(HardwareReportResponse response, Client client) {
} else { } else {
authSupportHardware.addPublicKeyToHardwareInfo(hardware, client.trustLevel.publicKey); authSupportHardware.addPublicKeyToHardwareInfo(hardware, client.trustLevel.publicKey);
} }
authSupportHardware.connectUserAndHardware(client.getUser(), hardware); authSupportHardware.connectUserAndHardware(client.sessionObject, hardware);
if (hardware.isBanned()) { if (hardware.isBanned()) {
throw new SecurityException("Your hardware banned"); throw new SecurityException("Your hardware banned");
} }
@ -108,7 +108,7 @@ public VerifySecureLevelKeyRequestEvent onSuccessVerify(Client client) {
throw new SecurityException("Your hardware banned"); throw new SecurityException("Your hardware banned");
} }
client.trustLevel.hardwareInfo = hardware.getHardwareInfo(); client.trustLevel.hardwareInfo = hardware.getHardwareInfo();
authSupportHardware.connectUserAndHardware(client.getUser(), hardware); authSupportHardware.connectUserAndHardware(client.sessionObject, hardware);
} else if (provider == null) { } else if (provider == null) {
logger.warn("HWIDProvider null. HardwareInfo not checked!"); logger.warn("HWIDProvider null. HardwareInfo not checked!");
} else { } else {

View file

@ -28,7 +28,7 @@ public void invoke(String... args) throws Exception {
AuthProviderPair pair = args.length > 1 ? server.config.getAuthProviderPair(args[1]) : server.config.getAuthProviderPair(); AuthProviderPair pair = args.length > 1 ? server.config.getAuthProviderPair(args[1]) : server.config.getAuthProviderPair();
ClientProfile profile = null; ClientProfile profile = null;
for(ClientProfile p : server.getProfiles()) { for(ClientProfile p : server.getProfiles()) {
if(p.getTitle().equals(args[0])) { if(p.getTitle().equals(args[0]) || p.getUUID().toString().equals(args[0])) {
profile = p; profile = p;
break; break;
} }
@ -40,7 +40,7 @@ public void invoke(String... args) throws Exception {
logger.error("AuthId {} not found", args[1]); logger.error("AuthId {} not found", args[1]);
return; return;
} }
String token = server.authManager.newCheckServerToken(args[0], pair.name); String token = server.authManager.newCheckServerToken(profile != null ? profile.getUUID().toString() : args[0], pair.name);
logger.info("Server token {} authId {}: {}", args[0], pair.name, token); logger.info("Server token {} authId {}: {}", args[0], pair.name, token);
} }
}); });
@ -48,7 +48,7 @@ public void invoke(String... args) throws Exception {
@Override @Override
public String getArgsDescription() { public String getArgsDescription() {
return "[new/info/token name] [args]"; return "[server/info/token name] [args]";
} }
@Override @Override

View file

@ -192,7 +192,6 @@ public void init(LaunchServer.ReloadType type) {
if (!type.equals(LaunchServer.ReloadType.NO_AUTH)) { if (!type.equals(LaunchServer.ReloadType.NO_AUTH)) {
for (AuthProviderPair pair : auth.values()) { for (AuthProviderPair pair : auth.values()) {
server.registerObject("auth.".concat(pair.name).concat(".core"), pair.core); server.registerObject("auth.".concat(pair.name).concat(".core"), pair.core);
server.registerObject("auth.".concat(pair.name).concat(".social"), pair.social);
server.registerObject("auth.".concat(pair.name).concat(".texture"), pair.textureProvider); server.registerObject("auth.".concat(pair.name).concat(".texture"), pair.textureProvider);
} }
} }
@ -203,7 +202,6 @@ public void close(LaunchServer.ReloadType type) {
try { try {
if (!type.equals(LaunchServer.ReloadType.NO_AUTH)) { if (!type.equals(LaunchServer.ReloadType.NO_AUTH)) {
for (AuthProviderPair pair : auth.values()) { for (AuthProviderPair pair : auth.values()) {
server.unregisterObject("auth.".concat(pair.name).concat(".social"), pair.social);
server.unregisterObject("auth.".concat(pair.name).concat(".core"), pair.core); server.unregisterObject("auth.".concat(pair.name).concat(".core"), pair.core);
server.unregisterObject("auth.".concat(pair.name).concat(".texture"), pair.textureProvider); server.unregisterObject("auth.".concat(pair.name).concat(".texture"), pair.textureProvider);
pair.close(); pair.close();

View file

@ -14,7 +14,6 @@
import pro.gravit.launchserver.auth.AuthException; import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.AuthCoreProvider; import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.core.AuthSocialProvider;
import pro.gravit.launchserver.auth.core.User; import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession; import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures;
@ -31,7 +30,6 @@
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
public class AuthManager { public class AuthManager {
private transient final LaunchServer server; private transient final LaunchServer server;
@ -86,7 +84,7 @@ public boolean accept(Client client, AuthProviderPair pair, String extendedToken
client.auth = server.config.getAuthProviderPair(info.authId); client.auth = server.config.getAuthProviderPair(info.authId);
if(client.permissions == null) client.permissions = new ClientPermissions(); if(client.permissions == null) client.permissions = new ClientPermissions();
client.permissions.addPerm("launchserver.checkserver"); client.permissions.addPerm("launchserver.checkserver");
client.permissions.addPerm(String.format("launchserver.profiles.%s.show", info.serverName)); client.permissions.addPerm(String.format("launchserver.profile.%s.show", info.serverName));
return true; return true;
} }
} }
@ -143,62 +141,27 @@ public AuthReport auth(AuthResponse.AuthContext context, AuthRequest.AuthPasswor
context.client.sessionObject = session; context.client.sessionObject = session;
internalAuth(context.client, context.authType, context.pair, user.getUsername(), user.getUUID(), user.getPermissions(), true); internalAuth(context.client, context.authType, context.pair, user.getUsername(), user.getUUID(), user.getPermissions(), true);
if (context.authType == AuthResponse.ConnectTypes.CLIENT && server.config.protectHandler.allowGetAccessToken(context)) { if (context.authType == AuthResponse.ConnectTypes.CLIENT && server.config.protectHandler.allowGetAccessToken(context)) {
return AuthReport.ofMinecraftAccessToken(user.getAccessToken()); return AuthReport.ofMinecraftAccessToken(user.getAccessToken(), session);
} }
return AuthReport.ofMinecraftAccessToken(null); return AuthReport.ofMinecraftAccessToken(null, session);
} }
User user = null;
boolean skipPasswordCheck = false;
String login = context.login; String login = context.login;
if (context.pair.social != null) { try {
AuthSocialProvider.SocialResult result = context.pair.social.preAuth(context, password); AuthReport result = provider.authorize(login, context, password, context.authType == AuthResponse.ConnectTypes.CLIENT && server.config.protectHandler.allowGetAccessToken(context));
if (result != null) { if(result == null || result.session == null || result.session.getUser() == null) {
if (result.user != null) user = result.user; logger.error("AuthCoreProvider {} method 'authorize' return null", context.pair.name);
if (result.login != null) login = result.login;
if (result.password != null) password = result.password;
if (result.user != null && result.password == null) skipPasswordCheck = true;
}
}
if (user == null && login != null) {
user = provider.getUserByLogin(context.login);
if (user == null) {
throw new AuthException(AuthRequestEvent.USER_NOT_FOUND_ERROR_MESSAGE);
}
}
AuthCoreProvider.PasswordVerifyReport report = null;
if (!skipPasswordCheck) {
report = provider.verifyPassword(user, password);
}
if (skipPasswordCheck || report.success) {
AuthReport result;
try {
result = provider.createOAuthSession(user, context, report, context.authType == AuthResponse.ConnectTypes.CLIENT && server.config.protectHandler.allowGetAccessToken(context));
} catch (IOException e) {
if (e instanceof AuthException) throw (AuthException) e;
logger.error(e);
throw new AuthException("Internal Auth Error"); throw new AuthException("Internal Auth Error");
} }
if (user == null) { var session = result.session;
if (result.session != null) { var user = session.getUser();
user = result.session.getUser();
} else {
logger.error("AuthCoreProvider {} method createOAuthSession returns null session with login null", context.pair.name);
throw new AuthException("Internal Auth Error");
}
}
context.client.coreObject = user; context.client.coreObject = user;
context.client.sessionObject = session;
internalAuth(context.client, context.authType, context.pair, user.getUsername(), user.getUUID(), user.getPermissions(), result.isUsingOAuth()); internalAuth(context.client, context.authType, context.pair, user.getUsername(), user.getUUID(), user.getPermissions(), result.isUsingOAuth());
return result; return result;
} else { } catch (IOException e) {
if (report.needMoreFactors) { if (e instanceof AuthException) throw (AuthException) e;
if (report.factors.size() == 1 && report.factors.get(0) == -1) { logger.error(e);
throw new AuthException(AuthRequestEvent.TWO_FACTOR_NEED_ERROR_MESSAGE); throw new AuthException("Internal Auth Error");
}
String message = AuthRequestEvent.ONE_FACTOR_NEED_ERROR_MESSAGE_PREFIX
.concat(report.factors.stream().map(String::valueOf).collect(Collectors.joining(".")));
throw new AuthException(message);
}
throw new AuthException(AuthRequestEvent.WRONG_PASSWORD_ERROR_MESSAGE);
} }
} }
@ -217,7 +180,6 @@ public void internalAuth(Client client, AuthResponse.ConnectTypes authType, Auth
client.type = authType; client.type = authType;
client.uuid = uuid; client.uuid = uuid;
client.useOAuth = oauth; client.useOAuth = oauth;
client.coreObject = pair.core.getUserByUUID(uuid);
} }
public CheckServerReport checkServer(Client client, String username, String serverID) throws IOException { public CheckServerReport checkServer(Client client, String username, String serverID) throws IOException {
@ -374,24 +336,16 @@ public record AuthReport(String minecraftAccessToken, String oauthAccessToken,
String oauthRefreshToken, long oauthExpire, String oauthRefreshToken, long oauthExpire,
UserSession session) { UserSession session) {
public static AuthReport ofOAuth(String oauthAccessToken, String oauthRefreshToken, long oauthExpire) {
return new AuthReport(null, oauthAccessToken, oauthRefreshToken, oauthExpire, null);
}
public static AuthReport ofOAuth(String oauthAccessToken, String oauthRefreshToken, long oauthExpire, UserSession session) { public static AuthReport ofOAuth(String oauthAccessToken, String oauthRefreshToken, long oauthExpire, UserSession session) {
return new AuthReport(null, oauthAccessToken, oauthRefreshToken, oauthExpire, session); return new AuthReport(null, oauthAccessToken, oauthRefreshToken, oauthExpire, session);
} }
public static AuthReport ofOAuthWithMinecraft(String minecraftAccessToken, String oauthAccessToken, String oauthRefreshToken, long oauthExpire) {
return new AuthReport(minecraftAccessToken, oauthAccessToken, oauthRefreshToken, oauthExpire, null);
}
public static AuthReport ofOAuthWithMinecraft(String minecraftAccessToken, String oauthAccessToken, String oauthRefreshToken, long oauthExpire, UserSession session) { public static AuthReport ofOAuthWithMinecraft(String minecraftAccessToken, String oauthAccessToken, String oauthRefreshToken, long oauthExpire, UserSession session) {
return new AuthReport(minecraftAccessToken, oauthAccessToken, oauthRefreshToken, oauthExpire, session); return new AuthReport(minecraftAccessToken, oauthAccessToken, oauthRefreshToken, oauthExpire, session);
} }
public static AuthReport ofMinecraftAccessToken(String minecraftAccessToken) { public static AuthReport ofMinecraftAccessToken(String minecraftAccessToken, UserSession session) {
return new AuthReport(minecraftAccessToken, null, null, 0, null); return new AuthReport(minecraftAccessToken, null, null, 0, session);
} }
public boolean isUsingOAuth() { public boolean isUsingOAuth() {

View file

@ -36,9 +36,14 @@ public class Client {
public Map<String, String> serializableProperties; public Map<String, String> serializableProperties;
public transient AtomicInteger refCount = new AtomicInteger(1); public transient AtomicInteger refCount;
public Client(UUID session) { public Client(UUID session) {
this(session, 1);
}
public Client(UUID session, int initialRefCount) {
refCount = new AtomicInteger(initialRefCount);
this.session = session; this.session = session;
timestamp = System.currentTimeMillis(); timestamp = System.currentTimeMillis();
type = null; type = null;

View file

@ -76,9 +76,9 @@ public static class AuthContext {
public final String profileName; public final String profileName;
public final String ip; public final String ip;
public final ConnectTypes authType; public final ConnectTypes authType;
public final Client client; public transient final Client client;
public final AuthProviderPair pair; public transient final AuthProviderPair pair;
public AuthManager.AuthReport report; public transient AuthManager.AuthReport report;
public AuthContext(Client client, String login, String profileName, String ip, ConnectTypes authType, AuthProviderPair pair) { public AuthContext(Client client, String login, String profileName, String ip, ConnectTypes authType, AuthProviderPair pair) {
this.client = client; this.client = client;

View file

@ -45,7 +45,7 @@ public void execute(ChannelHandlerContext ctx, Client client) {
sendError("Exit internal error"); sendError("Exit internal error");
return; return;
} }
Client newClient = new Client(null); Client newClient = new Client(null, 0);
newClient.checkSign = client.checkSign; newClient.checkSign = client.checkSign;
handler.setClient(newClient); handler.setClient(newClient);
AuthSupportExit supportExit = client.auth.core.isSupport(AuthSupportExit.class); AuthSupportExit supportExit = client.auth.core.isSupport(AuthSupportExit.class);

View file

@ -20,7 +20,7 @@ public void execute(ChannelHandlerContext ctx, Client client) {
List<GetAvailabilityAuthRequestEvent.AuthAvailability> list = new ArrayList<>(); List<GetAvailabilityAuthRequestEvent.AuthAvailability> list = new ArrayList<>();
for (AuthProviderPair pair : server.config.auth.values()) { for (AuthProviderPair pair : server.config.auth.values()) {
list.add(new GetAvailabilityAuthRequestEvent.AuthAvailability(pair.name, pair.displayName, list.add(new GetAvailabilityAuthRequestEvent.AuthAvailability(pair.name, pair.displayName,
pair.isUseSocial() ? pair.social.getDetails(client) : pair.core.getDetails(client))); pair.core.getDetails(client)));
} }
sendResult(new GetAvailabilityAuthRequestEvent(list)); sendResult(new GetAvailabilityAuthRequestEvent(list));
} }

View file

@ -6,8 +6,8 @@ public final class Version implements Comparable<Version> {
public static final int MAJOR = 5; public static final int MAJOR = 5;
public static final int MINOR = 2; public static final int MINOR = 2;
public static final int PATCH = 4; public static final int PATCH = 5;
public static final int BUILD = 2; public static final int BUILD = 1;
public static final Version.Type RELEASE = Type.STABLE; public static final Version.Type RELEASE = Type.STABLE;
public final int major; public final int major;
public final int minor; public final int minor;

View file

@ -163,7 +163,7 @@ public void run(String... args) throws Throwable {
LogHelper.error(e); LogHelper.error(e);
} }
}; };
LogHelper.info("ServerWrapper: Project %s, LaunchServer address: %s. Title: %s", config.projectname, config.address, Launcher.profile != null ? Launcher.profile.getTitle() : "unknown"); LogHelper.info("ServerWrapper: LaunchServer address: %s. Title: %s", config.address, Launcher.profile != null ? Launcher.profile.getTitle() : "unknown");
LogHelper.info("Minecraft Version (for profile): %s", wrapper.profile == null ? "unknown" : wrapper.profile.getVersion().name); LogHelper.info("Minecraft Version (for profile): %s", wrapper.profile == null ? "unknown" : wrapper.profile.getVersion().name);
LogHelper.info("Start Minecraft Server"); LogHelper.info("Start Minecraft Server");
LogHelper.debug("Invoke main method %s", mainClass.getName()); LogHelper.debug("Invoke main method %s", mainClass.getName());
@ -181,8 +181,7 @@ public void run(String... args) throws Throwable {
} }
public void updateLauncherConfig() { public void updateLauncherConfig() {
LauncherConfig cfg = new LauncherConfig(config.address, null, null, new HashMap<>(), "ServerWrapper");
LauncherConfig cfg = new LauncherConfig(config.address, null, null, new HashMap<>(), config.projectname);
Launcher.setConfig(cfg); Launcher.setConfig(cfg);
} }
@ -200,7 +199,6 @@ public void setConfig(Config config) {
public Config getDefaultConfig() { public Config getDefaultConfig() {
Config newConfig = new Config(); Config newConfig = new Config();
newConfig.serverName = "your server name"; newConfig.serverName = "your server name";
newConfig.projectname = "MineCraft";
newConfig.mainclass = ""; newConfig.mainclass = "";
newConfig.extendedTokens = new HashMap<>(); newConfig.extendedTokens = new HashMap<>();
newConfig.args = new ArrayList<>(); newConfig.args = new ArrayList<>();
@ -211,6 +209,7 @@ public Config getDefaultConfig() {
} }
public static final class Config { public static final class Config {
@Deprecated
public String projectname; public String projectname;
public String address; public String address;
public String serverName; public String serverName;

View file

@ -5,7 +5,7 @@
id 'org.openjfx.javafxplugin' version '0.0.10' apply false id 'org.openjfx.javafxplugin' version '0.0.10' apply false
} }
group = 'pro.gravit.launcher' group = 'pro.gravit.launcher'
version = '5.2.4' version = '5.2.5'
apply from: 'props.gradle' apply from: 'props.gradle'

@ -1 +1 @@
Subproject commit fd75e8dfa26a3103f03e4e6e6e3eb1c6e6a4ba1a Subproject commit dced154854c59d8b1e816ebfb1f51baaaeafabfb