Skip to content

Commit 9c2ae0d

Browse files
committed
Add saving of Database on Change
1 parent fc875d7 commit 9c2ae0d

8 files changed

Lines changed: 168 additions & 60 deletions

File tree

http/src/main/java/org/linguafranca/pwdb/keepasshttp/DatabaseAdaptor.java

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package org.linguafranca.pwdb.keepasshttp;
22

33
import org.apache.commons.codec.binary.Hex;
4+
import org.linguafranca.pwdb.Credentials;
45
import org.linguafranca.pwdb.Database;
56
import org.linguafranca.pwdb.kdbx.Helpers;
67
import org.linguafranca.pwdb.kdbx.simple.SimpleDatabase;
78
import org.spongycastle.crypto.digests.SHA1Digest;
89

910

11+
import java.io.*;
1012
import java.util.UUID;
1113

1214
/**
@@ -17,25 +19,31 @@ public interface DatabaseAdaptor {
1719
String getId();
1820
String getHash();
1921
Database getDatabase();
22+
PwGenerator getPwGenerator();
23+
OutputStream getOutputStream();
24+
Credentials getCredentials();
2025

2126
class Default implements DatabaseAdaptor {
22-
private String id = "402881E9-58B6-5A30-0158-B65A30B20000";
23-
private UUID rootGroupUuid = UUID.fromString("402881E9-58B6-5A30-0158-B65AFC580001");
24-
private UUID recycleBinUuid = UUID.fromString("402881E9-58B6-5A30-0158-B65BC8D30002");
2527
private Database database;
28+
private PwGenerator pwGenerator;
29+
private File databaseFile;
30+
private final Credentials credentials;
2631

27-
Default(Database database) {
28-
this.database = database;
32+
Default(File file, Credentials credentials, PwGenerator pwGenerator) throws Exception {
33+
this.databaseFile = file;
34+
this.pwGenerator = pwGenerator;
35+
this.credentials = credentials;
36+
this.database = SimpleDatabase.load(credentials, new FileInputStream(file));
2937
}
3038

3139
@Override
3240
public String getId() {
33-
return id;
41+
return database.getName() + " (" + database.getRootGroup().getUuid().toString() + ")";
3442
}
3543

3644
@Override
3745
public String getHash() {
38-
byte[] toHash = (Helpers.hexStringFromUuid(rootGroupUuid) + Helpers.hexStringFromUuid(recycleBinUuid)).getBytes();
46+
byte[] toHash = Helpers.hexStringFromUuid(database.getRootGroup().getUuid()).getBytes();
3947
SHA1Digest digest = new SHA1Digest();
4048
byte[] digestBytes = new byte[digest.getDigestSize()];
4149
digest.update(toHash, 0, toHash.length);
@@ -48,5 +56,24 @@ public String getHash() {
4856
public Database getDatabase() {
4957
return database;
5058
}
59+
60+
@Override
61+
public PwGenerator getPwGenerator() {
62+
return pwGenerator;
63+
}
64+
65+
@Override
66+
public OutputStream getOutputStream() {
67+
try {
68+
return new FileOutputStream(databaseFile);
69+
} catch (FileNotFoundException e) {
70+
throw new IllegalStateException(e);
71+
}
72+
}
73+
74+
@Override
75+
public Credentials getCredentials() {
76+
return credentials;
77+
}
5178
}
5279
}

http/src/main/java/org/linguafranca/pwdb/keepasshttp/KeePassHttpHandler.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@
2222
public class KeePassHttpHandler extends AbstractHandler {
2323

2424
private final DatabaseAdaptor adaptor;
25-
private Processor processor;
25+
private final Processor processor;
2626
private Logger logger = LoggerFactory.getLogger(KeePassHttpHandler.class);
27-
private Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create();
27+
private Gson gson = new GsonBuilder().disableHtmlEscaping().create();
2828
private Crypto crypto = new Crypto();
2929

30-
KeePassHttpHandler(Database database, PwGenerator pwGenerator) {
31-
this.adaptor = new DatabaseAdaptor.Default(database);
32-
this.processor = new Processor(database, pwGenerator);
30+
KeePassHttpHandler(DatabaseAdaptor adaptor) {
31+
this.adaptor = adaptor;
32+
this.processor = new Processor(adaptor);
3333
}
3434

3535
@Override
@@ -64,11 +64,11 @@ public void handle(String s, Request request, HttpServletRequest httpServletRequ
6464
if (!crypto.verify(request1)) {
6565
logger.debug("Request failed verification");
6666
response.Success = false;
67-
response.Error = "";
6867
} else {
6968
try {
7069
processor.process(request1, response);
7170
crypto.makeVerifiable(response);
71+
response.Id = adaptor.getId();
7272
} catch (Exception e) {
7373
httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
7474
response.Success = false;
@@ -82,5 +82,4 @@ public void handle(String s, Request request, HttpServletRequest httpServletRequ
8282
writer.flush();
8383
request.setHandled(true);
8484
}
85-
8685
}

http/src/main/java/org/linguafranca/pwdb/keepasshttp/KeePassHttpServer.java

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,44 @@
22

33
import org.eclipse.jetty.server.Server;
44
import org.eclipse.jetty.server.ServerConnector;
5+
import org.linguafranca.pwdb.kdbx.KdbxCreds;
56
import org.linguafranca.pwdb.kdbx.simple.SimpleDatabase;
7+
import org.linguafranca.pwdb.keepasshttp.PwGenerator.HexPwGenerator;
68

9+
import java.io.File;
10+
import java.io.FileOutputStream;
11+
import java.nio.file.Files;
12+
import java.nio.file.Paths;
713
import java.security.SecureRandom;
814
import java.util.Random;
915

1016
/**
1117
* Embedded server
1218
*/
1319
public class KeePassHttpServer {
20+
private static final String DEFAULT_DB_FILE = "HttpDatabase.kdbx";
1421
public static void main(String[] args) throws Exception {
22+
1523
Server server = new Server();
24+
1625
ServerConnector http = new ServerConnector(server);
1726
http.setHost("0.0.0.0");
1827
http.setPort(19455);
1928
http.setIdleTimeout(300000);
2029
server.addConnector(http);
21-
server.setHandler(new KeePassHttpHandler(new SimpleDatabase(), new PwGenerator() {
22-
@Override
23-
public String generate() {
24-
String[] symbols = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
25-
int length = 10;
26-
Random random = new SecureRandom();
27-
StringBuilder sb = new StringBuilder(length);
28-
for (int i = 0; i < length; i++) {
29-
int indexRandom = random.nextInt( symbols.length );
30-
sb.append( symbols[indexRandom] );
31-
}
32-
return sb.toString();
33-
}
34-
}));
30+
31+
KdbxCreds creds = new KdbxCreds("123".getBytes());
32+
// create a database if we don't have one already
33+
if (Files.notExists(Paths.get(DEFAULT_DB_FILE))) {
34+
SimpleDatabase db = new SimpleDatabase();
35+
db.setName("HTTP Database");
36+
db.save(creds,new FileOutputStream(DEFAULT_DB_FILE));
37+
}
38+
39+
DatabaseAdaptor adaptor = new DatabaseAdaptor.Default(new File(DEFAULT_DB_FILE), creds, new HexPwGenerator(10));
40+
server.setHandler(new KeePassHttpHandler(adaptor));
41+
3542
server.start();
3643
server.join();
3744
}
38-
}
45+
}

http/src/main/java/org/linguafranca/pwdb/keepasshttp/Message.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ public static class Verifiable {
4040
* Nonce value used in conjunction with all encrypted fields,
4141
* randomly generated for each request
4242
*/
43-
public String Nonce="";
43+
public String Nonce;
4444

4545
/**
4646
* Used to check that the correct key has been chosen
4747
*/
48-
public String Verifier="";
48+
public String Verifier;
4949
}
5050

5151
/**
@@ -122,20 +122,20 @@ public Response(String request, String hash) {
122122
*/
123123
public String RequestType;
124124

125-
public String Error = "";
125+
public String Error;
126126

127127
public boolean Success = false;
128128

129129
/**
130130
* The user selected String as a result of 'associate',
131131
* always returned on every request
132132
*/
133-
public String Id="";
133+
public String Id;
134134

135135
/**
136136
* response to get-logins-count, number of entries for requested Url
137137
*/
138-
public Integer Count;
138+
public Integer Count=0;
139139

140140
/**
141141
* response the current version of KeePassHttp

http/src/main/java/org/linguafranca/pwdb/keepasshttp/Processor.java

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.linguafranca.pwdb.Entry;
55
import org.linguafranca.pwdb.keepasshttp.Message.ResponseEntry;
66

7+
import java.io.IOException;
78
import java.text.SimpleDateFormat;
89
import java.util.*;
910

@@ -20,12 +21,16 @@ private interface MessageProcessor {
2021

2122
private final Database database;
2223
private final PwGenerator pwGenerator;
24+
private final DatabaseAdaptor adaptor;
25+
2326
private Map<String, MessageProcessor> processors = new HashMap<>();
2427

2528

26-
Processor(Database database, PwGenerator pwGenerator) {
27-
this.database = database;
28-
this.pwGenerator = pwGenerator;
29+
Processor(DatabaseAdaptor adaptor) {
30+
this.database = adaptor.getDatabase();
31+
this.pwGenerator = adaptor.getPwGenerator();
32+
this.adaptor = adaptor;
33+
2934
processors.put(Message.Type.TEST_ASSOCIATE, new TestAssociate());
3035
processors.put(Message.Type.ASSOCIATE, new Associate());
3136
processors.put(Message.Type.GET_LOGINS, new GetLogins());
@@ -39,10 +44,6 @@ void process(Message.Request request, Message.Response response) {
3944
processors.get(request.RequestType).process(request, response);
4045
}
4146

42-
private String makeId() {
43-
return database.getName() + " (" + database.getRootGroup().getUuid().toString() + ")";
44-
}
45-
4647
private class GetLogins implements MessageProcessor {
4748
public void process(final Message.Request r, Message.Response resp) {
4849

@@ -58,7 +59,6 @@ public boolean matches(Entry entry) {
5859
resp.Entries.add(new ResponseEntry(entry.getTitle(), entry.getUsername(), entry.getPassword(), entry.getUuid().toString()));
5960
}
6061
resp.Count = resp.Entries.size();
61-
resp.Id = makeId();
6262
resp.Success = true;
6363
}
6464
}
@@ -75,7 +75,6 @@ public void process(Message.Request r, Message.Response resp) {
7575
String p = pwGenerator.generate();
7676
resp.Entries.add(new ResponseEntry("Password", "login", p, UUID.randomUUID().toString()));
7777
resp.Count = resp.Entries.size();
78-
resp.Id = makeId();
7978
resp.Success = true;
8079
}
8180
}
@@ -93,7 +92,6 @@ public boolean matches(Entry entry) {
9392
resp.Entries.add(new ResponseEntry(entry.getTitle(), entry.getUsername(), entry.getPassword(), entry.getUuid().toString()));
9493
}
9594
resp.Count = resp.Entries.size();
96-
resp.Id = makeId();
9795
resp.Success = true;
9896
}
9997
}
@@ -115,23 +113,28 @@ public boolean matches(Entry entry) {
115113
if (entry == null) {
116114
entry = database.newEntry();
117115
entry.setTitle("New Entry " + format.format(new Date()));
116+
entry.setNotes("Created automatically");
117+
} else {
118+
entry.setNotes(entry.getNotes() + "\nUpdated " + format.format(new Date()));
118119
}
119120
entry.setPassword(r.Password);
120121
entry.setUsername(r.Login);
121122
entry.setUrl(r.Url);
122123
entry.setProperty("SubmitUrl", r.SubmitUrl);
123-
entry.setNotes("Created automatically");
124124
//noinspection unchecked
125125
database.getRootGroup().addEntry(entry);
126-
resp.Id = makeId();
126+
try {
127+
database.save(adaptor.getCredentials(), adaptor.getOutputStream());
128+
} catch (IOException e) {
129+
throw new IllegalStateException(e);
130+
}
127131
resp.Success = true;
128132
}
129133
}
130134

131135
private class Associate implements MessageProcessor {
132136
@Override
133137
public void process(Message.Request request, Message.Response response) {
134-
response.Id = makeId();
135138
response.Success = true;
136139
}
137140

@@ -142,8 +145,7 @@ private class TestAssociate implements MessageProcessor {
142145
public void process(Message.Request request, Message.Response response) {
143146
response.Success = false;
144147
if (request.Id != null) {
145-
response.Success = request.Id.equals(makeId());
146-
response.Id = makeId();
148+
response.Success = request.Id.equals(adaptor.getId());
147149
}
148150
}
149151

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,33 @@
11
package org.linguafranca.pwdb.keepasshttp;
22

3+
import java.security.SecureRandom;
4+
import java.util.Random;
5+
36
/**
47
* @author jo
58
*/
69
public interface PwGenerator {
710
String generate();
11+
/**
12+
* Generator for simple hex password
13+
*/
14+
class HexPwGenerator implements PwGenerator {
15+
private final int length;
16+
17+
HexPwGenerator (int length) {
18+
this.length = length;
19+
}
20+
21+
@Override
22+
public String generate() {
23+
String[] symbols = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
24+
Random random = new SecureRandom();
25+
StringBuilder sb = new StringBuilder(length);
26+
for (int i = 0; i < length; i++) {
27+
int indexRandom = random.nextInt( symbols.length );
28+
sb.append( symbols[indexRandom] );
29+
}
30+
return sb.toString();
31+
}
32+
}
833
}

0 commit comments

Comments
 (0)