diff --git a/core/pom.xml b/core/pom.xml
new file mode 100644
index 0000000..08fbe3a
--- /dev/null
+++ b/core/pom.xml
@@ -0,0 +1,15 @@
+
+
+ 4.0.0
+
+ ru.kirillius
+ pf-sdn
+ 0.1.0.0
+
+
+ core
+
+
+
\ No newline at end of file
diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Auth/AuthManager.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Auth/AuthManager.java
new file mode 100644
index 0000000..b3e38ed
--- /dev/null
+++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Auth/AuthManager.java
@@ -0,0 +1,72 @@
+package ru.kirillius.pf.sdn.core.Auth;
+
+import jakarta.servlet.http.HttpSession;
+import ru.kirillius.pf.sdn.core.Context;
+import ru.kirillius.pf.sdn.core.Util.HashUtil;
+
+import java.util.Objects;
+
+public class AuthManager {
+ private final Context context;
+ public final static String SESSION_AUTH_KEY = "auth";
+ public final static String SESSION_TOKEN = "token";
+
+ public AuthManager(Context context) {
+ this.context = context;
+ }
+
+ public boolean validatePassword(String pass) {
+ var config = context.getConfig();
+ return HashUtil.hash(pass, config.getPasswordSalt()).equals(config.getPasswordHash());
+ }
+
+ public void updatePassword(String pass) {
+ var config = context.getConfig();
+ config.setPasswordHash(
+ HashUtil.hash(pass, config.getPasswordSalt())
+ );
+ }
+
+ public AuthToken createToken(String description) {
+ var config = context.getConfig();
+ var token = new AuthToken();
+ token.setDescripton(description);
+ config.getTokens().add(token);
+ config.update();
+ return token;
+ }
+
+ public boolean validateToken(AuthToken token) {
+ return context.getConfig().getTokens().contains(token);
+ }
+
+ public void setSessionAuthState(HttpSession session, boolean state) {
+ session.setAttribute(SESSION_AUTH_KEY, state);
+ }
+
+ public void setSessionToken(HttpSession session, AuthToken token) {
+ session.setAttribute(SESSION_TOKEN, token);
+ }
+
+ public AuthToken getSessionToken(HttpSession session) {
+ return (AuthToken) session.getAttribute(SESSION_TOKEN);
+ }
+
+ public boolean getSessionAuthState(HttpSession session) {
+ return Objects.equals(session.getAttribute(SESSION_AUTH_KEY), Boolean.TRUE);
+ }
+
+ public void invalidateToken(AuthToken token) {
+ var config = context.getConfig();
+ config.getTokens().remove(token);
+ config.update();
+ }
+
+ public boolean validateToken(String token) {
+ return validateToken(new AuthToken(token));
+ }
+
+ public void invalidateToken(String token) {
+ invalidateToken(new AuthToken(token));
+ }
+}
diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Auth/AuthToken.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Auth/AuthToken.java
new file mode 100644
index 0000000..6b992a6
--- /dev/null
+++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Auth/AuthToken.java
@@ -0,0 +1,40 @@
+package ru.kirillius.pf.sdn.core.Auth;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import ru.kirillius.json.JSONProperty;
+import ru.kirillius.json.JSONSerializable;
+
+import java.util.Objects;
+import java.util.UUID;
+
+@NoArgsConstructor
+@JSONSerializable
+public class AuthToken {
+ public AuthToken(String token) {
+ this.token = token;
+ }
+
+ @Setter
+ @Getter
+ @JSONProperty
+ private String descripton = "untitled";
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) return false;
+ AuthToken authToken = (AuthToken) o;
+ return Objects.equals(token, authToken.token);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(token);
+ }
+
+ @Setter
+ @Getter
+ @JSONProperty
+ private String token = UUID.randomUUID().toString();
+}
\ No newline at end of file
diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Config.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Config.java
new file mode 100644
index 0000000..b0e5f9d
--- /dev/null
+++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Config.java
@@ -0,0 +1,84 @@
+package ru.kirillius.pf.sdn.core;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+import ru.kirillius.json.JSONArrayProperty;
+import ru.kirillius.json.JSONProperty;
+import ru.kirillius.json.JSONSerializable;
+import ru.kirillius.json.JSONUtility;
+import ru.kirillius.pf.sdn.core.Auth.AuthToken;
+
+import java.io.*;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+@NoArgsConstructor
+@JSONSerializable
+public class Config {
+ @Getter
+ private File loadedConfigFile = null;
+ @Setter
+ @Getter
+ @JSONProperty
+ private String host = "0.0.0.0";
+
+ @Setter
+ @Getter
+ @JSONArrayProperty(type = AuthToken.class)
+ private List tokens = Collections.emptyList();
+
+ @Setter
+ @Getter
+ @JSONProperty
+ private String passwordSalt = UUID.randomUUID().toString();
+
+ @Setter
+ @Getter
+ @JSONProperty
+ private String passwordHash = null;
+
+ @Setter
+ @Getter
+ @JSONProperty
+ private int httpPort = 8081;
+
+ public void update() {
+ try {
+ store(this, loadedConfigFile);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void store(Config config, File file) throws IOException {
+ try (var fileInputStream = new FileOutputStream(file)) {
+ try (var writer = new BufferedWriter(new OutputStreamWriter(fileInputStream))) {
+ writer.write(serialize(config).toString());
+ writer.flush();
+ }
+ }
+ }
+
+ public static JSONObject serialize(Config config) {
+ return JSONUtility.serializeStructure(config);
+ }
+
+ public static Config deserialize(JSONObject object) {
+ return JSONUtility.deserializeStructure(object, Config.class);
+ }
+
+ public static Config load(File file) throws IOException {
+ try (var stream = new FileInputStream(file)) {
+ var json = new JSONObject(new JSONTokener(stream));
+ var config = deserialize(json);
+ config.loadedConfigFile = file;
+ return config;
+ }
+ }
+
+
+}
diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Context.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Context.java
new file mode 100644
index 0000000..a029ce5
--- /dev/null
+++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Context.java
@@ -0,0 +1,13 @@
+package ru.kirillius.pf.sdn.core;
+
+import org.eclipse.jetty.server.Server;
+import ru.kirillius.pf.sdn.core.Auth.AuthManager;
+
+public interface Context {
+ Config getConfig();
+
+ AuthManager getAuthManager();
+
+ Server getServer();
+
+}
diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/IPv4Subnet.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/IPv4Subnet.java
new file mode 100644
index 0000000..a007038
--- /dev/null
+++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Networking/IPv4Subnet.java
@@ -0,0 +1,56 @@
+package ru.kirillius.pf.sdn.core.Networking;
+
+import lombok.Getter;
+import ru.kirillius.pf.sdn.core.Util.IPv4Util;
+
+import java.util.regex.Pattern;
+
+
+public class IPv4Subnet {
+
+ private final long address;
+ @Getter
+ private final int prefixLength;
+
+
+ public IPv4Subnet(String subnet) {
+ var split = subnet.split(Pattern.quote("/"));
+ if (split.length != 2) {
+ throw new IllegalArgumentException("Invalid subnet: " + subnet);
+ }
+ var prefix = Integer.parseInt(split[1]);
+ IPv4Util.validatePrefix(prefix);
+
+ address = IPv4Util.ipAddressToLong(split[0]);
+ prefixLength = prefix;
+ }
+
+
+ public IPv4Subnet(String address, int prefixLength) {
+ IPv4Util.validatePrefix(prefixLength);
+
+ this.address = IPv4Util.ipAddressToLong(address);
+ this.prefixLength = prefixLength;
+ }
+
+ public String getAddress() {
+ return IPv4Util.longToIpAddress(address);
+ }
+
+ @Override
+ public String toString() {
+ return getAddress() + '/' + prefixLength;
+ }
+
+ public boolean overlaps(IPv4Subnet subnet) {
+ var minPrefixLength = Math.min(prefixLength, subnet.prefixLength);
+ var commonMask = IPv4Util.calculateMask(minPrefixLength);
+ if (commonMask != prefixLength) {
+ return false; //can't overlap larger prefix
+ }
+
+ return (address & commonMask) == (subnet.address & commonMask);
+ }
+
+
+}
diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Util/BGPUtility.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Util/BGPUtility.java
new file mode 100644
index 0000000..d5df553
--- /dev/null
+++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Util/BGPUtility.java
@@ -0,0 +1,5 @@
+package ru.kirillius.pf.sdn.core.Util;
+
+public class BGPUtility {
+
+}
diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Util/HashUtil.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Util/HashUtil.java
new file mode 100644
index 0000000..dd62def
--- /dev/null
+++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Util/HashUtil.java
@@ -0,0 +1,30 @@
+package ru.kirillius.pf.sdn.core.Util;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public final class HashUtil {
+ private HashUtil() {
+ }
+
+
+ public static String hash(String data, String salt) {
+ String generatedPassword = null;
+ MessageDigest md = null;
+ try {
+ md = MessageDigest.getInstance("SHA-512");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ md.update(salt.getBytes(StandardCharsets.UTF_8));
+ var bytes = md.digest(data.getBytes(StandardCharsets.UTF_8));
+ var sb = new StringBuilder();
+ for (byte aByte : bytes) {
+ sb.append(Integer.toString((aByte & 0xff) + 0x100, 16).substring(1));
+ }
+ generatedPassword = sb.toString();
+ return generatedPassword;
+
+ }
+}
diff --git a/core/src/main/java/ru/kirillius/pf/sdn/core/Util/IPv4Util.java b/core/src/main/java/ru/kirillius/pf/sdn/core/Util/IPv4Util.java
new file mode 100644
index 0000000..358ee3c
--- /dev/null
+++ b/core/src/main/java/ru/kirillius/pf/sdn/core/Util/IPv4Util.java
@@ -0,0 +1,53 @@
+package ru.kirillius.pf.sdn.core.Util;
+
+import lombok.SneakyThrows;
+
+import java.net.InetAddress;
+import java.util.regex.Pattern;
+
+
+public class IPv4Util {
+
+ private IPv4Util() {
+ }
+
+ private static final Pattern pattern = Pattern.compile("^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
+
+ public static void validateAddress(String address) {
+ if (!pattern.matcher(address).matches()) {
+ throw new IllegalArgumentException("Invalid IPv4 address: " + address);
+ }
+ }
+
+ public static void validatePrefix(int prefix) {
+ if (prefix < 0 || prefix > 32) {
+ throw new IllegalArgumentException("Invalid IPv4 prefix: " + prefix);
+ }
+ }
+
+
+ @SneakyThrows
+ public static long ipAddressToLong(String address) {
+ validateAddress(address);
+ var ip = InetAddress.getByName(address);
+ var bytes = ip.getAddress();
+ var result = 0L;
+ for (var b : bytes) {
+ result = (result << 8) | (b & 0xFF);
+ }
+ return result;
+ }
+
+ public static long calculateMask(int prefixLength) {
+ validatePrefix(prefixLength);
+ return 0xFFFFFFFFL << (32 - prefixLength);
+ }
+
+
+ public static String longToIpAddress(long ipLong) {
+ if (ipLong < 0 || ipLong > 0xFFFFFFFFL) {
+ throw new IllegalArgumentException("Address number should be in range 0 - 4294967295");
+ }
+ return ((ipLong >> 24) & 0xFF) + "." + ((ipLong >> 16) & 0xFF) + "." + ((ipLong >> 8) & 0xFF) + "." + (ipLong & 0xFF);
+ }
+}
diff --git a/core/src/test/java/ru/kirillius/pf/sdn/core/Networking/IPv4SubnetTest.java b/core/src/test/java/ru/kirillius/pf/sdn/core/Networking/IPv4SubnetTest.java
new file mode 100644
index 0000000..078471c
--- /dev/null
+++ b/core/src/test/java/ru/kirillius/pf/sdn/core/Networking/IPv4SubnetTest.java
@@ -0,0 +1,77 @@
+package ru.kirillius.pf.sdn.core.Networking;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class IPv4SubnetTest {
+ @SuppressWarnings("CatchMayIgnoreException")
+ @Test
+ void testInit() {
+ var validPrefixes = List.of(0, 5, 20, 32);
+ var invalidPrefixes = List.of(-1, 33, 800);
+ var validAddresses = List.of("1.2.3.4", "0.0.0.0", "255.255.255.255");
+ var invalidAddresses = List.of("1.2.3.04", "0.0.0", "255.255.255.255.255", "1.2.3.256", "-1.0.0.0");
+
+
+ validPrefixes.forEach(prefix -> {
+ validAddresses.forEach(address -> {
+ var subnet = new IPv4Subnet(address, prefix);
+ });
+ });
+
+ invalidPrefixes.forEach(prefix -> {
+ validAddresses.forEach(address -> {
+ try {
+ var subnet = new IPv4Subnet(address, prefix);
+ throw new AssertionError();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalArgumentException.class);
+ }
+ });
+ });
+
+ validPrefixes.forEach(prefix -> {
+ invalidAddresses.forEach(address -> {
+ try {
+ var subnet = new IPv4Subnet(address, prefix);
+ throw new AssertionError();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalArgumentException.class);
+ }
+ });
+ });
+
+ invalidPrefixes.forEach(prefix -> {
+ invalidAddresses.forEach(address -> {
+ try {
+ var subnet = new IPv4Subnet(address, prefix);
+ throw new AssertionError();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(IllegalArgumentException.class);
+ }
+ });
+ });
+
+ }
+
+ @Test
+ void overlaps() {
+ checkOverlaps(
+ new IPv4Subnet("0.0.0.0", 0),
+ new IPv4Subnet("8.8.8.8", 32)
+ );
+
+ checkOverlaps(
+ new IPv4Subnet("192.168.0.0", 24),
+ new IPv4Subnet("192.168.0.128", 25)
+ );
+ }
+
+ private void checkOverlaps(IPv4Subnet larger, IPv4Subnet smaller) {
+ assertThat(larger.overlaps(smaller)).isTrue();
+ assertThat(smaller.overlaps(larger)).isFalse();
+ }
+}
\ No newline at end of file
diff --git a/launcher/pom.xml b/launcher/pom.xml
new file mode 100644
index 0000000..9126b2c
--- /dev/null
+++ b/launcher/pom.xml
@@ -0,0 +1,14 @@
+
+
+ 4.0.0
+
+ ru.kirillius
+ pf-sdn
+ 0.1.0.0
+
+
+ launcher
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 03009d9..268337f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,11 +7,114 @@
ru.kirillius
pf-sdn
0.1.0.0
+ pom
+
+ web-client
+ launcher
+ core
+ web-server
+
- 24
- 24
+ 21
+ 21
UTF-8
+
+
+
+ kirillius
+ kirillius
+ https://repo.kirillius.ru/maven
+
+ true
+ always
+ fail
+
+ default
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.13.0-M2
+ test
+
+
+
+
+ org.assertj
+ assertj-core
+ 4.0.0-M1
+ test
+
+
+
+ org.javatuples
+ javatuples
+ 1.2
+
+
+ com.cronutils
+ cron-utils
+ 9.2.0
+
+
+
+ org.snmp4j
+ snmp4j
+ 3.7.7
+
+
+ ru.kirillius
+ icmp4j
+ 1.0.0.0
+
+
+ ru.kirillius.util
+ dynamic-types
+ 2.0.0.0
+
+
+ ru.kirillius
+ json-rpc-servlet
+ 2.1.4.0
+
+
+ ru.kirillius.utils
+ common-logging
+ 1.3.0.0
+
+
+ ru.kirillius
+ hibernate-commons
+ 2.2.0.0
+
+
+
+ org.javassist
+ javassist
+ 3.29.2-GA
+
+
+
+
+ org.eclipse.jetty
+ jetty-server
+ 12.0.12
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.34
+ provided
+
+
+
\ No newline at end of file
diff --git a/web-client/pom.xml b/web-client/pom.xml
new file mode 100644
index 0000000..3da5878
--- /dev/null
+++ b/web-client/pom.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+ ru.kirillius
+ pf-sdn
+ 0.1.0.0
+
+
+ web-client
+
+
+
+
\ No newline at end of file
diff --git a/web-server/pom.xml b/web-server/pom.xml
new file mode 100644
index 0000000..66ec4c9
--- /dev/null
+++ b/web-server/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+ ru.kirillius
+ pf-sdn
+ 0.1.0.0
+
+
+ web-server
+
+
+ ru.kirillius
+ core
+ 0.1.0.0
+ compile
+
+
+
+
+
\ No newline at end of file
diff --git a/web-server/src/main/java/ru/kirillius/pf/sdn/web/HTTPServer.java b/web-server/src/main/java/ru/kirillius/pf/sdn/web/HTTPServer.java
new file mode 100644
index 0000000..22713f5
--- /dev/null
+++ b/web-server/src/main/java/ru/kirillius/pf/sdn/web/HTTPServer.java
@@ -0,0 +1,91 @@
+package ru.kirillius.pf.sdn.web;
+
+
+import org.eclipse.jetty.ee10.servlet.DefaultServlet;
+import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.jetbrains.annotations.Nullable;
+import ru.kirillius.json.rpc.Servlet.JSONRPCServlet;
+import ru.kirillius.pf.sdn.core.Context;
+import ru.kirillius.utils.logging.SystemLogger;
+import ru.kirillius.pf.sdn.web.RPC.AuthRPC;
+import ru.kirillius.pf.sdn.web.RPC.RPC;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Objects;
+import java.util.Set;
+
+public class HTTPServer extends Server {
+
+ public final static String ANY_HOST = "0.0.0.0";
+ private final static String LOG_CONTEXT = HTTPServer.class.getSimpleName();
+ private final static String TOKEN_HEADER = "X-Auth-token";
+
+ private final JSONRPCServlet JSONRPC = new JSONRPCServlet();
+
+ private String getResourceBase() throws MalformedURLException {
+ var resourceFile = getClass().getClassLoader().getResource("htdocs/index.html");
+ return new URL(Objects.requireNonNull(resourceFile).getProtocol(), resourceFile.getHost(), resourceFile.getPath()
+ .substring(0, resourceFile.getPath().lastIndexOf("/")))
+ .toString();
+ }
+
+ private final static Set> RPCHandlerTypes = Set.of(AuthRPC.class);
+
+
+ public HTTPServer(Context appContext, int port, @Nullable String host) throws Exception {
+
+ var connector = new ServerConnector(this);
+ connector.setPort(port);
+ if (host != null && !host.equals(ANY_HOST)) {
+ connector.setHost(host);
+ }
+
+ this.addConnector(connector);
+ ServletContextHandler servletContext = new ServletContextHandler("/", ServletContextHandler.SESSIONS);
+ servletContext.addServlet(JSONRPC, JSONRPCServlet.CONTEXT_PATH);
+ var holder = servletContext.addServlet(DefaultServlet.class, "/");
+ holder.setInitParameter("resourceBase", getResourceBase());
+ this.setHandler(servletContext);
+
+ start();
+
+
+ JSONRPC.addRequestHandler((request, response, call) -> {
+ var authManager = appContext.getAuthManager();
+ var authorized = authManager.getSessionAuthState(call.getContext().getSession());
+ // Thread.sleep(100);//FIXME remove! debug only
+
+ //auth by token
+ if (!authorized) {
+ var headerToken = request.getHeader(TOKEN_HEADER);
+ if (headerToken != null) {
+ authorized = authManager.validateToken(headerToken);
+ authManager.setSessionAuthState(call.getContext().getSession(), authorized);
+ }
+ }
+
+
+ var isProtectedAccess = call.getMethod().getAnnotation(ProtectedMethod.class);
+ if (isProtectedAccess != null) {
+ if (!authorized) throw new SecurityException("Forbidden");
+ }
+ });
+
+
+ for (var handlerClass : RPCHandlerTypes) {
+ var instance = RPC.instantiate(handlerClass, appContext);
+ //noinspection unchecked
+ JSONRPC.addTargetInstance((Class super RPC>) handlerClass, instance);
+ }
+
+
+ JSONRPC.getErrorHandler().add(throwable -> {
+ SystemLogger.error("JRPC Request " +
+ (throwable.getRequestData() == null ? "" : throwable.getRequestData().toString()) +
+ " has failed with error", LOG_CONTEXT, throwable.getError());
+ });
+ }
+}
diff --git a/web-server/src/main/java/ru/kirillius/pf/sdn/web/ProtectedMethod.java b/web-server/src/main/java/ru/kirillius/pf/sdn/web/ProtectedMethod.java
new file mode 100644
index 0000000..0eef9f4
--- /dev/null
+++ b/web-server/src/main/java/ru/kirillius/pf/sdn/web/ProtectedMethod.java
@@ -0,0 +1,11 @@
+package ru.kirillius.pf.sdn.web;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface ProtectedMethod {
+}
diff --git a/web-server/src/main/java/ru/kirillius/pf/sdn/web/RPC/AuthRPC.java b/web-server/src/main/java/ru/kirillius/pf/sdn/web/RPC/AuthRPC.java
new file mode 100644
index 0000000..49e81be
--- /dev/null
+++ b/web-server/src/main/java/ru/kirillius/pf/sdn/web/RPC/AuthRPC.java
@@ -0,0 +1,52 @@
+package ru.kirillius.pf.sdn.web.RPC;
+
+import ru.kirillius.json.rpc.Annotations.JRPCArgument;
+import ru.kirillius.json.rpc.Annotations.JRPCContext;
+import ru.kirillius.json.rpc.Annotations.JRPCMethod;
+import ru.kirillius.json.rpc.Servlet.CallContext;
+import ru.kirillius.pf.sdn.core.Auth.AuthToken;
+import ru.kirillius.pf.sdn.core.Context;
+import ru.kirillius.pf.sdn.web.ProtectedMethod;
+
+public class AuthRPC implements RPC {
+ private final Context context;
+
+ public AuthRPC(Context context) {
+ this.context = context;
+ }
+
+ @ProtectedMethod
+ @JRPCMethod
+ public AuthToken getAuthToken(@JRPCContext CallContext call) {
+ return context.getAuthManager().getSessionToken(call.getSession());
+ }
+
+ @ProtectedMethod
+ @JRPCMethod
+ public AuthToken rememberCurrentUser(@JRPCContext CallContext call) {
+ var UA = call.getRequest().getHeader("User-Agent");
+ if (UA == null) {
+ UA = "Unknown user agent";
+ }
+ var authManager = context.getAuthManager();
+ var token = authManager.createToken(UA);
+ authManager.setSessionToken(call.getSession(), token);
+ return token;
+ }
+
+ @JRPCMethod
+ public boolean auth(@JRPCArgument(name = "password") String password, @JRPCContext CallContext call) {
+ var authManager = context.getAuthManager();
+ if (authManager.validatePassword(password)) {
+ authManager.setSessionAuthState(call.getSession(), true);
+ }
+ return false;
+ }
+
+
+ @JRPCMethod
+ public void logout(@JRPCContext CallContext call) {
+ var authManager = context.getAuthManager();
+ authManager.setSessionAuthState(call.getSession(), false);
+ }
+}
diff --git a/web-server/src/main/java/ru/kirillius/pf/sdn/web/RPC/RPC.java b/web-server/src/main/java/ru/kirillius/pf/sdn/web/RPC/RPC.java
new file mode 100644
index 0000000..2d2272e
--- /dev/null
+++ b/web-server/src/main/java/ru/kirillius/pf/sdn/web/RPC/RPC.java
@@ -0,0 +1,16 @@
+package ru.kirillius.pf.sdn.web.RPC;
+
+import ru.kirillius.pf.sdn.core.Context;
+
+import java.lang.reflect.InvocationTargetException;
+
+public interface RPC {
+ static T instantiate(Class type, Context context) {
+ try {
+ return type.getConstructor(Context.class).newInstance(context);
+ } catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
+ IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}