Move HTML export logic to a separate file and finish it up

This commit is contained in:
Alexander Bakker 2022-12-05 21:44:01 +01:00
parent 2c36149a3d
commit 7a1e4e1d77
3 changed files with 120 additions and 88 deletions

View File

@ -0,0 +1,109 @@
package com.beemdevelopment.aegis.vault;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.util.Base64;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.Hex;
import com.beemdevelopment.aegis.helpers.QrCodeHelper;
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.MotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.YandexInfo;
import com.google.common.html.HtmlEscapers;
import com.google.zxing.WriterException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collection;
public class VaultHtmlExporter {
private VaultHtmlExporter() {
}
public static void export(Context context, PrintStream ps, Collection<VaultEntry> entries) throws WriterException, IOException {
ps.print("<html><head><title>");
ps.print(context.getString(R.string.export_html_title));
ps.print("</title></head><body>");
ps.print("<h1>");
ps.print(context.getString(R.string.export_html_title));
ps.print("</h1>");
ps.print("<table>");
ps.print("<tr>");
ps.print("<th>Issuer</th>");
ps.print("<th>Name</th>");
ps.print("<th>Type</th>");
ps.print("<th>QR Code</th>");
ps.print("<th>UUID</th>");
ps.print("<th>Note</th>");
ps.print("<th>Favorite</th>");
ps.print("<th>Algo</th>");
ps.print("<th>Digits</th>");
ps.print("<th>Secret</th>");
ps.print("<th>Counter</th>");
ps.print("<th>PIN</th>");
ps.print("</tr>");
for (VaultEntry entry : entries) {
ps.print("<tr>");
OtpInfo info = entry.getInfo();
GoogleAuthInfo gaInfo = new GoogleAuthInfo(info, entry.getName(), entry.getIssuer());
appendRow(ps, entry.getIssuer());
appendRow(ps, entry.getName());
appendRow(ps, info.getType());
appendQrRow(ps, gaInfo.getUri().toString());
appendRow(ps, entry.getUUID().toString());
appendRow(ps, entry.getNote());
appendRow(ps, Boolean.toString(entry.isFavorite()));
appendRow(ps, info.getAlgorithm(false));
appendRow(ps, Integer.toString(info.getDigits()));
if (info instanceof MotpInfo) {
appendRow(ps, Hex.encode(info.getSecret()));
} else {
appendRow(ps, Base32.encode(info.getSecret()));
}
if (info instanceof HotpInfo) {
appendRow(ps, Long.toString(((HotpInfo) info).getCounter()));
} else {
appendRow(ps, "-");
}
if (info instanceof YandexInfo) {
appendRow(ps, ((YandexInfo) info).getPin());
} else if (info instanceof MotpInfo) {
appendRow(ps, ((MotpInfo) info).getPin());
} else {
appendRow(ps, "-");
}
ps.print("</tr>");
}
ps.print("</table></body>");
ps.print("<style>table,td,th{border:1px solid #000;border-collapse:collapse;text-align:center}td:not(.qr),th{padding:1em}</style>");
ps.print("</html>");
}
private static void appendRow(PrintStream ps, String s) {
ps.print("<td>");
ps.print(escape(s));
ps.print("</td>");
}
private static void appendQrRow(PrintStream ps, String s) throws IOException, WriterException {
ps.print("<td class='qr'><img src=\"data:image/png;base64,");
Bitmap bm = QrCodeHelper.encodeToBitmap(s, 256, 256, Color.WHITE);
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
String encoded = Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT);
ps.print(encoded);
}
ps.print("\"/></td>");
}
private static String escape(String s) {
return HtmlEscapers.htmlEscaper().escape(s);
}
}

View File

@ -1,28 +1,18 @@
package com.beemdevelopment.aegis.vault;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.util.Base64;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.AtomicFile;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.helpers.QrCodeHelper;
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.util.IOUtils;
import com.google.common.html.HtmlEscapers;
import com.google.zxing.WriterException;
import org.json.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -32,9 +22,9 @@ import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.text.Collator;
import java.util.Collection;
import java.util.Objects;
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors;
public class VaultRepository {
public static final String FILENAME = "aegis.json";
@ -215,82 +205,15 @@ public class VaultRepository {
* Username and QR Code and writing it to the given OutputStream.
*/
public void exportHtml(OutputStream outStream, @Nullable Vault.EntryFilter filter) throws VaultRepositoryException {
try {
PrintStream printStream = new PrintStream(outStream, false, StandardCharsets.UTF_8.name());
printStream.print("<html><head><title>");
printStream.print(_context.getString(R.string.export_html_title));
printStream.print("</title></head><body>");
printStream.print("<h1>");
printStream.print(_context.getString(R.string.export_html_title));
printStream.print("</h1>");
printStream.print("<table>");
printStream.print("<tr>");
printStream.print("<th>Issuer</th>");
printStream.print("<th>Username</th>");
printStream.print("<th>Type</th>");
printStream.print("<th>QR Code</th>");
printStream.print("<th>UUID</th>");
printStream.print("<th>Note</th>");
printStream.print("<th>Favorite</th>");
printStream.print("<th>Algo</th>");
printStream.print("<th>Digits</th>");
printStream.print("<th>Secret</th>");
printStream.print("<th>Counter</th>");
printStream.print("</tr>");
for (VaultEntry entry : getEntries()) {
if (filter == null || filter.includeEntry(entry)) {
printStream.print("<tr>");
GoogleAuthInfo info = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer());
OtpInfo otpInfo = info.getOtpInfo();
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(info.getIssuer()));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(entry.getName()));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(otpInfo.getType()));
printStream.print("</td>");
Bitmap bm = QrCodeHelper.encodeToBitmap(info.getUri().toString(),256, 256, Color.WHITE);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] b = baos.toByteArray();
String encodedImage = Base64.encodeToString(b, Base64.DEFAULT);
printStream.print("<td class='qr'><img src=\"data:image/png;base64,");
printStream.print(encodedImage);
printStream.print("\"/></td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(entry.getUUID().toString()));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(entry.getNote()));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(entry.isFavorite() ? "true" : "false"));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(otpInfo.getAlgorithm(false)));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(Integer.toString(otpInfo.getDigits())));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(Base32.encode(otpInfo.getSecret())));
printStream.print("</td>");
printStream.print("<td>");
if (Objects.equals(otpInfo.getTypeId(), HotpInfo.ID)) {
printStream.print(HtmlEscapers.htmlEscaper().escape(Long.toString(((HotpInfo) otpInfo).getCounter())));
} else {
printStream.print("-");
}
printStream.print("</td>");
printStream.print("</tr>");
}
};
printStream.print("</table></body>");
printStream.print("<style>table,td,th{border:1px solid #000;border-collapse:collapse;text-align:center}td:not(.qr),th{padding:1em}</style>");
printStream.print("</html>");
printStream.flush();
Collection<VaultEntry> entries = getEntries();
if (filter != null) {
entries = entries.stream()
.filter(filter::includeEntry)
.collect(Collectors.toList());
}
try (PrintStream ps = new PrintStream(outStream, false, StandardCharsets.UTF_8.name())) {
VaultHtmlExporter.export(_context, ps, entries);
} catch (WriterException | IOException e) {
throw new VaultRepositoryException(e);
}

View File

@ -110,7 +110,7 @@
<string name="export_all_groups">Export all groups</string>
<string name="export_choose_groups">Select which groups to export:</string>
<string name="export_no_groups_selected">No groups selected to export</string>
<string name="export_html_title">Aegis Authenticator Export</string>
<string name="export_html_title" context="The title of an HTML export document">Aegis Authenticator Export</string>
<string name="choose_authentication_method">Security</string>
<string name="authentication_method_explanation">Aegis is a security-focused 2FA app. Tokens are stored in a vault, that can optionally be encrypted with a password of your choosing. If an attacker obtains your encrypted vault file, they will not be able to access the contents without knowing the password.\n\nWe\'ve preselected the option that we think would fit best for your device.</string>