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