промежуточный коммит
This commit is contained in:
parent
c9506224ea
commit
08c8fe2f7c
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>pf-sdn</artifactId>
|
||||
<version>0.1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>core</artifactId>
|
||||
|
||||
|
||||
</project>
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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<AuthToken> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package ru.kirillius.pf.sdn.core.Util;
|
||||
|
||||
public class BGPUtility {
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>pf-sdn</artifactId>
|
||||
<version>0.1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>launcher</artifactId>
|
||||
|
||||
</project>
|
||||
107
pom.xml
107
pom.xml
|
|
@ -7,11 +7,114 @@
|
|||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>pf-sdn</artifactId>
|
||||
<version>0.1.0.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>web-client</module>
|
||||
<module>launcher</module>
|
||||
<module>core</module>
|
||||
<module>web-server</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>24</maven.compiler.source>
|
||||
<maven.compiler.target>24</maven.compiler.target>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>kirillius</id>
|
||||
<name>kirillius</name>
|
||||
<url>https://repo.kirillius.ru/maven</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
<updatePolicy>always</updatePolicy>
|
||||
<checksumPolicy>fail</checksumPolicy>
|
||||
</releases>
|
||||
<layout>default</layout>
|
||||
</repository>
|
||||
</repositories>
|
||||
<dependencies>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.13.0-M2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.assertj/assertj-core -->
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>4.0.0-M1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.javatuples</groupId>
|
||||
<artifactId>javatuples</artifactId>
|
||||
<version>1.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cronutils</groupId>
|
||||
<artifactId>cron-utils</artifactId>
|
||||
<version>9.2.0</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.snmp4j/snmp4j -->
|
||||
<dependency>
|
||||
<groupId>org.snmp4j</groupId>
|
||||
<artifactId>snmp4j</artifactId>
|
||||
<version>3.7.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>icmp4j</artifactId>
|
||||
<version>1.0.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius.util</groupId>
|
||||
<artifactId>dynamic-types</artifactId>
|
||||
<version>2.0.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>json-rpc-servlet</artifactId>
|
||||
<version>2.1.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius.utils</groupId>
|
||||
<artifactId>common-logging</artifactId>
|
||||
<version>1.3.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>hibernate-commons</artifactId>
|
||||
<version>2.2.0.0</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
<version>3.29.2-GA</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-server -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>12.0.12</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.34</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>pf-sdn</artifactId>
|
||||
<version>0.1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>web-client</artifactId>
|
||||
|
||||
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>pf-sdn</artifactId>
|
||||
<version>0.1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>web-server</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>0.1.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
|
|
@ -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<Class<? extends RPC>> 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());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 extends RPC> T instantiate(Class<T> type, Context context) {
|
||||
try {
|
||||
return type.getConstructor(Context.class).newInstance(context);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
|
||||
IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue