Skip to content

Commit 331cc7d

Browse files
registry-init v0.0.1
1 parent 5c8300d commit 331cc7d

File tree

9 files changed

+535
-2
lines changed

9 files changed

+535
-2
lines changed

buildPlugins/src/main/groovy/conventions.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ dependencies {
5454
constraints {
5555
[
5656
['org.slf4j:slf4j-bom', '2.0.17'],
57-
['org.bouncycastle:bcpkix-jdk18on', '1.81'],
57+
// https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk18on
58+
['org.bouncycastle:bcprov-jdk18on', '1.83'],
59+
// https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk18on
60+
['org.bouncycastle:bcpkix-jdk18on', '1.83'],
5861
].each { l ->
5962
['implementation'].each { String c ->
6063
add(c, l.getFirst()) { version { prefer l.getLast() } }

cloud/util/registry-init/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# registry-init
2+
3+
this is a cli app that packages up [../registry](../registry) into a "portable" cli.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
plugins {
2+
id 'spring-conventions'
3+
id 'jib-conventions'
4+
id 'picocli-conventions'
5+
}
6+
7+
version = '0.0.1'
8+
9+
application.mainClass = 'side.cloud.util.registry.init.RegistryInitCli'
10+
11+
dependencies {
12+
implementation 'org.bouncycastle:bcprov-jdk18on'
13+
implementation 'org.bouncycastle:bcpkix-jdk18on'
14+
implementation 'com.fasterxml.jackson.core:jackson-databind'
15+
implementation project(':lib:picocli-lib')
16+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package side.cloud.util.registry.init;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.SneakyThrows;
5+
import org.bouncycastle.crypto.generators.OpenBSDBCrypt;
6+
7+
import java.io.IOException;
8+
import java.nio.charset.StandardCharsets;
9+
import java.nio.file.Files;
10+
import java.nio.file.OpenOption;
11+
import java.nio.file.Path;
12+
import java.security.SecureRandom;
13+
import java.util.List;
14+
15+
import static java.nio.file.StandardOpenOption.*;
16+
17+
@RequiredArgsConstructor
18+
public class HtpasswdCrud {
19+
final SecureRandom secureRandom;
20+
21+
@SneakyThrows
22+
public List<String> list(Path htpasswd) {
23+
return Files.readAllLines(htpasswd);
24+
}
25+
26+
public void create(Path htpasswd, String username, String password) {
27+
create(htpasswd, username, password, false);
28+
}
29+
30+
@SneakyThrows
31+
public void create(Path htpasswd, String username, String password, boolean mustBeNew) {
32+
String contents;
33+
if (mustBeNew) {
34+
contents = "";
35+
} else
36+
try {
37+
contents = Files.readString(htpasswd);
38+
} catch (IOException e) {
39+
contents = "";
40+
}
41+
42+
String usernamePrefix = username + ":";
43+
contents.lines()
44+
.filter(l -> l.startsWith(usernamePrefix))
45+
.findAny()
46+
.ifPresent(ignored -> {
47+
throw new IllegalArgumentException("username already exists");
48+
});
49+
50+
boolean needsNewLine =
51+
!contents.isEmpty() && contents.charAt(contents.length() - 1) != '\n';
52+
53+
try (var out = Files.newOutputStream(htpasswd,
54+
mustBeNew
55+
? new OpenOption[]{CREATE_NEW}
56+
: new OpenOption[]{APPEND, CREATE})) {
57+
out.write(
58+
((needsNewLine ? "\n" : "")
59+
+ usernamePrefix
60+
+ bcrypt(password)
61+
+ "\n")
62+
.getBytes(StandardCharsets.UTF_8)
63+
);
64+
}
65+
}
66+
67+
@SneakyThrows
68+
public String read(Path htpasswd, String username) {
69+
String usernamePrefix = username + ":";
70+
71+
return Files.readAllLines(htpasswd).stream()
72+
.filter(e -> e.startsWith(usernamePrefix))
73+
.findAny()
74+
.orElseThrow(() ->
75+
new IllegalArgumentException("username does not exist for reading"));
76+
}
77+
78+
@SneakyThrows
79+
public void update(Path htpasswd, String username, String password) {
80+
List<String> lines = Files.readAllLines(htpasswd);
81+
String usernamePrefix = username + ":";
82+
83+
for (int i = 0; i < lines.size(); i++) {
84+
if (lines.get(i).startsWith(usernamePrefix)) {
85+
lines.set(i, usernamePrefix + bcrypt(password));
86+
Files.writeString(htpasswd, String.join("\n", lines) + "\n");
87+
return;
88+
}
89+
}
90+
91+
throw new IllegalArgumentException("username does not exist for update");
92+
}
93+
94+
@SneakyThrows
95+
public void delete(Path htpasswd, String username) {
96+
List<String> lines = Files.readAllLines(htpasswd);
97+
String usernamePrefix = username + ":";
98+
99+
List<String> withoutUser =
100+
lines.stream()
101+
.filter(l -> !l.startsWith(usernamePrefix))
102+
.toList();
103+
104+
if (lines.size() == withoutUser.size()) {
105+
throw new IllegalArgumentException("username does not exist for delete");
106+
}
107+
108+
Files.writeString(htpasswd, String.join("\n", withoutUser) + "\n");
109+
}
110+
111+
public String bcrypt(String password) {
112+
return OpenBSDBCrypt.generate(password.toCharArray(), generateSalt(16), 10);
113+
}
114+
115+
public byte[] generateSalt(int bytes) {
116+
byte[] salt = new byte[bytes];
117+
secureRandom.nextBytes(salt);
118+
return salt;
119+
}
120+
}

0 commit comments

Comments
 (0)