-
-
Notifications
You must be signed in to change notification settings - Fork 393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Proton Pass importer #1438
base: master
Are you sure you want to change the base?
Add Proton Pass importer #1438
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package com.beemdevelopment.aegis.importers; | ||
|
||
import android.content.Context; | ||
import android.net.Uri; | ||
|
||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo; | ||
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException; | ||
import com.beemdevelopment.aegis.vault.VaultEntry; | ||
import com.beemdevelopment.aegis.vault.VaultGroup; | ||
import com.topjohnwu.superuser.io.SuFile; | ||
|
||
import org.json.JSONArray; | ||
import org.json.JSONException; | ||
import org.json.JSONObject; | ||
import org.json.JSONTokener; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.InputStreamReader; | ||
import java.util.Iterator; | ||
import java.util.zip.ZipEntry; | ||
import java.util.zip.ZipInputStream; | ||
|
||
public class ProtonPassImporter extends DatabaseImporter { | ||
public ProtonPassImporter(Context context) { | ||
super(context); | ||
} | ||
|
||
@Override | ||
protected SuFile getAppPath() { | ||
throw new UnsupportedOperationException(); | ||
} | ||
|
||
|
||
@Override | ||
protected State read(InputStream stream, boolean isInternal) throws DatabaseImporterException { | ||
// Unzip | ||
ZipInputStream zis = new ZipInputStream(stream); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrap this in a try-with-resources statement. |
||
|
||
// Read file from zip | ||
ZipEntry zipEntry; | ||
try { | ||
while((zipEntry = zis.getNextEntry()) != null) | ||
{ | ||
if(!zipEntry.getName().equals("Proton Pass/data.json")) | ||
{ | ||
continue; | ||
} | ||
|
||
// Read file | ||
BufferedReader br = new BufferedReader(new InputStreamReader(zis)); | ||
StringBuilder json = new StringBuilder(); | ||
String line; | ||
while((line = br.readLine()) != null){ | ||
json.append(line); | ||
} | ||
br.close(); | ||
Comment on lines
+52
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace this block with |
||
|
||
// Parse JSON | ||
JSONTokener tokener = new JSONTokener(json.toString()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can instantiate a |
||
JSONObject jsonObject = new JSONObject(tokener); | ||
|
||
return new State(jsonObject); | ||
} | ||
}catch (IOException | JSONException e) | ||
{ | ||
throw new DatabaseImporterException(e); | ||
} | ||
|
||
//Json not found | ||
throw new DatabaseImporterException("Invalid proton zip file"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Unable to find \"Proton Pass/data.json\" in the ZIP file." |
||
} | ||
|
||
public static class State extends DatabaseImporter.State { | ||
private JSONObject _jsonObject; | ||
|
||
private State(JSONObject jsonObject) | ||
{ | ||
super(false); | ||
_jsonObject = jsonObject; | ||
} | ||
|
||
public Result convert() throws DatabaseImporterException { | ||
Result result = new Result(); | ||
|
||
try { | ||
JSONObject vaults = this._jsonObject.getJSONObject("vaults"); | ||
Iterator<String> keys = vaults.keys(); | ||
|
||
// Iterate over vaults | ||
while (keys.hasNext()) | ||
{ | ||
JSONObject vault = vaults.getJSONObject(keys.next()); | ||
JSONArray items = vault.getJSONArray("items"); | ||
|
||
//Create a new group | ||
VaultGroup group = new VaultGroup(vault.getString("name")); | ||
result.addGroup(group); | ||
|
||
// Iterate over items on the vault | ||
for(int j = 0; j < items.length(); j++) | ||
{ | ||
JSONObject item = items.getJSONObject(j); | ||
|
||
try{ | ||
VaultEntry entry = this.fromItem(item); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To stay a bit more consistent with the other importers, please rename Check whether it's a TOTP item here instead of returning null from |
||
|
||
if(entry == null) | ||
{ | ||
continue; | ||
} | ||
|
||
entry.addGroup(group.getUUID()); | ||
result.addEntry(entry); | ||
}catch (JSONException | GoogleAuthInfoException e) | ||
{ | ||
result.addError(new DatabaseImporterEntryException(e, "Can't import " + item.getString("itemId"))); | ||
} | ||
} | ||
} | ||
|
||
return result; | ||
}catch (JSONException e) | ||
{ | ||
throw new DatabaseImporterException(e); | ||
} | ||
} | ||
|
||
public VaultEntry fromItem(JSONObject item) throws JSONException, GoogleAuthInfoException { | ||
JSONObject data = item.getJSONObject("data"); | ||
JSONObject metadata = data.getJSONObject("metadata"); | ||
JSONObject content = data.getJSONObject("content"); | ||
|
||
//Only login items | ||
if(!data.getString("type").equals("login")) | ||
{ | ||
return null; | ||
} | ||
|
||
|
||
String uri = content.getString("totpUri"); | ||
if(uri.isEmpty()) | ||
{ | ||
return null; | ||
} | ||
|
||
Uri toptURI = Uri.parse(content.getString("totpUri")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
GoogleAuthInfo entry = GoogleAuthInfo.parseUri(toptURI); | ||
|
||
return new VaultEntry(entry.getOtpInfo(), metadata.getString("name"), entry.getIssuer()); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -506,6 +506,7 @@ | |
<string name="importer_help_authy">Supply a copy of <b>/data/data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml</b>, located in the internal storage directory of Authy.</string> | ||
<string name="importer_help_andotp">Supply an andOTP export/backup file.</string> | ||
<string name="importer_help_bitwarden">Supply a Bitwarden export/backup file. Encrypted files are not supported.</string> | ||
<string name="importer_help_proton_pass">Supply a Proton pass export/backup zip. Encrypted files are not supported.</string> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Supply a Proton Pass export ZIP containing a JSON file. Encrypted files are not supported." |
||
<string name="importer_help_battle_net_authenticator">Supply a copy of <b>/data/data/com.blizzard.messenger/shared_prefs/com.blizzard.messenger.authenticator_preferences.xml</b>, located in the internal storage directory of Battle.net Authenticator.</string> | ||
<string name="importer_help_duo">Supply a copy of <b>/data/data/com.duosecurity.duomobile/files/duokit/accounts.json</b>, located in the internal storage directory of DUO.</string> | ||
<string name="importer_help_freeotp">Supply a copy of <b>/data/data/org.fedorahosted.freeotp/shared_prefs/tokens.xml</b>, located in the internal storage directory of FreeOTP (1.x).</string> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your importer does not support reading directly from the Proton Pass' internal storage, so the boolean at the end should be
false
.