initial meowing
This commit is contained in:
commit
87e29f0ce0
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
target
|
43
flake.lock
Normal file
43
flake.lock
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1656928814,
|
||||||
|
"narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1700612854,
|
||||||
|
"narHash": "sha256-yrQ8osMD+vDLGFX7pcwsY/Qr5PUd6OmDMYJZzZi0+zc=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "19cbff58383a4ae384dea4d1d0c823d72b49d614",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
36
flake.nix
Normal file
36
flake.nix
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
|
let
|
||||||
|
out = system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ self.overlays.default ];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.default = (pkgs.buildFHSEnv {
|
||||||
|
name = "s4d-env";
|
||||||
|
targetPkgs = pkgs: (with pkgs; [
|
||||||
|
jdk
|
||||||
|
zlib
|
||||||
|
freetype
|
||||||
|
fontconfig
|
||||||
|
maven
|
||||||
|
protobuf
|
||||||
|
]);
|
||||||
|
runScript = "zsh";
|
||||||
|
}).env;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
flake-utils.lib.eachDefaultSystem out // {
|
||||||
|
overlays.default = final: prev: {
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
108
plugin/pom.xml
Normal file
108
plugin/pom.xml
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<?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>
|
||||||
|
|
||||||
|
<groupId>systems.lix.keycloak</groupId>
|
||||||
|
<artifactId>plugin</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>19</maven.compiler.source>
|
||||||
|
<maven.compiler.target>19</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<keycloak.version>23.0.2</keycloak.version>
|
||||||
|
<maven.compiler.version>3.11.0</maven.compiler.version>
|
||||||
|
<maven.compiler.release>17</maven.compiler.release>
|
||||||
|
<maven-shade.version>3.2.4</maven-shade.version>
|
||||||
|
<maven-surefire.version>3.1.2</maven-surefire.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-server-spi</artifactId>
|
||||||
|
<version>${keycloak.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-server-spi-private</artifactId>
|
||||||
|
<version>${keycloak.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-services</artifactId>
|
||||||
|
<version>${keycloak.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>*</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>2.0.12</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-server-spi</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-server-spi-private</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-services</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${maven.compiler.version}</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>${maven-shade.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<artifactSet>
|
||||||
|
<excludes>
|
||||||
|
<exclude>org.keycloak*</exclude>
|
||||||
|
<exclude>org.apache.httpcomponents:httpcore</exclude>
|
||||||
|
<exclude>com.google*</exclude>
|
||||||
|
<exclude>org.checkerframework*</exclude>
|
||||||
|
</excludes>
|
||||||
|
</artifactSet>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>${maven-surefire.version}</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
72
plugin/src/main/java/lix/systems/keycloak/AllowBanCheck.java
Normal file
72
plugin/src/main/java/lix/systems/keycloak/AllowBanCheck.java
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package lix.systems.keycloak;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||||
|
import org.keycloak.authentication.AuthenticationFlowError;
|
||||||
|
import org.keycloak.authentication.Authenticator;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class AllowBanCheck implements Authenticator {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(AllowBanCheck.class);
|
||||||
|
private final AllowBansDB allowBansDB;
|
||||||
|
|
||||||
|
public AllowBanCheck(AllowBansDB allowBansDB) {
|
||||||
|
this.allowBansDB = allowBansDB;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void authenticate(AuthenticationFlowContext context) {
|
||||||
|
// Requires mapper that puts it into a githubId field on the user.
|
||||||
|
// The reason that we don't use the external ID link is that people can delete those.
|
||||||
|
var attr = context.getUser().getFirstAttribute("githubId");
|
||||||
|
|
||||||
|
if (attr == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowBansDB.isUserBannedById(attr)) {
|
||||||
|
context.getEvent().error("User is banned");
|
||||||
|
var challenge = context.form().setError("User is banned!").createErrorPage(Response.Status.UNAUTHORIZED);
|
||||||
|
context.failure(AuthenticationFlowError.ACCESS_DENIED, challenge);
|
||||||
|
return;
|
||||||
|
} else if (allowBansDB.isUsingAllowList() && !allowBansDB.isUserExplicitlyAllowedById(attr)) {
|
||||||
|
context.getEvent().error("User is not allow-listed");
|
||||||
|
var challenge = context.form().setError("User is not allow-listed!").createErrorPage(Response.Status.UNAUTHORIZED);
|
||||||
|
context.failure(AuthenticationFlowError.ACCESS_DENIED, challenge);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void action(AuthenticationFlowContext authenticationFlowContext) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresUser() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean configuredFor(KeycloakSession keycloakSession, RealmModel realmModel, UserModel userModel) {
|
||||||
|
var attr = userModel.getFirstAttribute("githubId");
|
||||||
|
|
||||||
|
return attr != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequiredActions(KeycloakSession keycloakSession, RealmModel realmModel, UserModel userModel) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package lix.systems.keycloak;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.authentication.Authenticator;
|
||||||
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** buddy i just work in the allow ban check factory */
|
||||||
|
public class AllowBanCheckFactory implements AuthenticatorFactory {
|
||||||
|
private Path dbPath = null;
|
||||||
|
public static final String PROVIDER_ID = "allow-ban-check-authenticator";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Allow/ban check";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getReferenceCategory() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfigurable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||||
|
return REQUIREMENTS_CHOICES;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final AuthenticationExecutionModel.Requirement[] REQUIREMENTS_CHOICES = {
|
||||||
|
AuthenticationExecutionModel.Requirement.REQUIRED,
|
||||||
|
AuthenticationExecutionModel.Requirement.DISABLED,
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserSetupAllowed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Meow meow meow meow meow";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authenticator create(KeycloakSession keycloakSession) {
|
||||||
|
return new AllowBanCheck(new FileAllowBansDB(dbPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope scope) {
|
||||||
|
dbPath = Path.of(scope.get("dbpath"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
}
|
21
plugin/src/main/java/lix/systems/keycloak/AllowBansDB.java
Normal file
21
plugin/src/main/java/lix/systems/keycloak/AllowBansDB.java
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package lix.systems.keycloak;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A database of whether users are allow-listed or banned.
|
||||||
|
*/
|
||||||
|
public interface AllowBansDB {
|
||||||
|
/**
|
||||||
|
* Gets if a user is in the allow list by ID.
|
||||||
|
*/
|
||||||
|
boolean isUserExplicitlyAllowedById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets if a user is in the ban list by ID.
|
||||||
|
*/
|
||||||
|
boolean isUserBannedById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to only allow users on allow-list to get in.
|
||||||
|
*/
|
||||||
|
boolean isUsingAllowList();
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package lix.systems.keycloak;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File based allow/ban lists implementation.
|
||||||
|
* The file in use has two space separated columns, ID and USERNAME respectively
|
||||||
|
*/
|
||||||
|
public class FileAllowBansDB implements AllowBansDB {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(FileAllowBansDB.class);
|
||||||
|
private final Path basePath;
|
||||||
|
|
||||||
|
public FileAllowBansDB(Path basePath) {
|
||||||
|
this.basePath = basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> idsInFile(Path file) throws FileNotFoundException {
|
||||||
|
// Kinda yolo, we probably should care more, but our ban list and allow list are never going to be long
|
||||||
|
var reader = new BufferedReader(new FileReader(file.toFile()));
|
||||||
|
return reader.lines().filter(l -> !l.startsWith("#") && !l.isEmpty()).map(l -> l.split(" ")[0]).collect(Collectors.toUnmodifiableSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserExplicitlyAllowedById(String id) {
|
||||||
|
var subpath = basePath.resolve("allowed-users.txt");
|
||||||
|
try {
|
||||||
|
var ids = idsInFile(subpath);
|
||||||
|
return ids.contains(id);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
LOG.error("Missing file {}", subpath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserBannedById(String id) {
|
||||||
|
var subpath = basePath.resolve("banned-users.txt");
|
||||||
|
try {
|
||||||
|
var ids = idsInFile(subpath);
|
||||||
|
return ids.contains(id);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
LOG.error("Missing file {}", subpath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUsingAllowList() {
|
||||||
|
return basePath.resolve("use-allow-list.txt").toFile().exists();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue