let users confirm each member in a conference even if that contact is already trusted

This commit is contained in:
Daniel Gultsch 2016-03-01 11:26:59 +01:00
parent 134c75ae01
commit 198dc2c6b4
6 changed files with 137 additions and 52 deletions

View File

@ -284,7 +284,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) { private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
HashSet<XmppAxolotlSession> sessions = new HashSet<>(); HashSet<XmppAxolotlSession> sessions = new HashSet<>();
for(Jid jid : getCryptoTargets(conversation)) { for(Jid jid : conversation.getAcceptedCryptoTargets()) {
sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values()); sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values());
} }
return sessions; return sessions;

View File

@ -9,11 +9,13 @@ import net.java.otr4j.session.SessionID;
import net.java.otr4j.session.SessionImpl; import net.java.otr4j.session.SessionImpl;
import net.java.otr4j.session.SessionStatus; import net.java.otr4j.session.SessionStatus;
import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.security.interfaces.DSAPublicKey; import java.security.interfaces.DSAPublicKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
@ -48,6 +50,7 @@ public class Conversation extends AbstractEntity implements Blockable {
public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password"; public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password";
public static final String ATTRIBUTE_MUTED_TILL = "muted_till"; public static final String ATTRIBUTE_MUTED_TILL = "muted_till";
public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify"; public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify";
public static final String ATTRIBUTE_CRYPTO_TARGETS = "crypto_targets";
private String name; private String name;
private String contactUuid; private String contactUuid;
@ -313,6 +316,18 @@ public class Conversation extends AbstractEntity implements Blockable {
return getLongAttribute("last_clear_history", 0); return getLongAttribute("last_clear_history", 0);
} }
public List<Jid> getAcceptedCryptoTargets() {
if (mode == MODE_SINGLE) {
return Arrays.asList(getJid().toBareJid());
} else {
return getJidListAttribute(ATTRIBUTE_CRYPTO_TARGETS);
}
}
public void setAcceptedCryptoTargets(List<Jid> acceptedTargets) {
setAttribute(ATTRIBUTE_CRYPTO_TARGETS, acceptedTargets);
}
public void setCorrectingMessage(Message correctingMessage) { public void setCorrectingMessage(Message correctingMessage) {
this.correctingMessage = correctingMessage; this.correctingMessage = correctingMessage;
} }
@ -794,22 +809,61 @@ public class Conversation extends AbstractEntity implements Blockable {
} }
public boolean setAttribute(String key, String value) { public boolean setAttribute(String key, String value) {
try { synchronized (this.attributes) {
this.attributes.put(key, value); try {
return true; this.attributes.put(key, value);
} catch (JSONException e) { return true;
return false; } catch (JSONException e) {
return false;
}
}
}
public boolean setAttribute(String key, List<Jid> jids) {
JSONArray array = new JSONArray();
for(Jid jid : jids) {
array.put(jid.toBareJid().toString());
}
synchronized (this.attributes) {
try {
this.attributes.put(key, array);
return true;
} catch (JSONException e) {
e.printStackTrace();
return false;
}
} }
} }
public String getAttribute(String key) { public String getAttribute(String key) {
try { synchronized (this.attributes) {
return this.attributes.getString(key); try {
} catch (JSONException e) { return this.attributes.getString(key);
return null; } catch (JSONException e) {
return null;
}
} }
} }
public List<Jid> getJidListAttribute(String key) {
ArrayList<Jid> list = new ArrayList<>();
synchronized (this.attributes) {
try {
JSONArray array = this.attributes.getJSONArray(key);
for (int i = 0; i < array.length(); ++i) {
try {
list.add(Jid.fromString(array.getString(i)));
} catch (InvalidJidException e) {
//ignored
}
}
} catch (JSONException e) {
//ignored
}
}
return list;
}
public int getIntAttribute(String key, int defaultValue) { public int getIntAttribute(String key, int defaultValue) {
String value = this.getAttribute(key); String value = this.getAttribute(key);
if (value == null) { if (value == null) {

View File

@ -1531,11 +1531,12 @@ public class ConversationActivity extends XmppActivity
protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) { protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) {
AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService(); AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService();
final List<Jid> targets = axolotlService.getCryptoTargets(mSelectedConversation); final List<Jid> targets = axolotlService.getCryptoTargets(mSelectedConversation);
boolean hasUnaccepted = !mSelectedConversation.getAcceptedCryptoTargets().containsAll(targets);
boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED).isEmpty(); boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED).isEmpty();
boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, targets).isEmpty(); boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, targets).isEmpty();
boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty(); boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty();
boolean hasNoTrustedKeys = axolotlService.anyTargetHasNoTrustedKeys(targets); boolean hasNoTrustedKeys = axolotlService.anyTargetHasNoTrustedKeys(targets);
if(hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys) { if(hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys || hasUnaccepted) {
axolotlService.createSessionsIfNeeded(mSelectedConversation); axolotlService.createSessionsIfNeeded(mSelectedConversation);
Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class); Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class);
String[] contacts = new String[targets.size()]; String[] contacts = new String[targets.size()];
@ -1545,6 +1546,7 @@ public class ConversationActivity extends XmppActivity
intent.putExtra("contacts", contacts); intent.putExtra("contacts", contacts);
intent.putExtra(EXTRA_ACCOUNT, mSelectedConversation.getAccount().getJid().toBareJid().toString()); intent.putExtra(EXTRA_ACCOUNT, mSelectedConversation.getAccount().getJid().toBareJid().toString());
intent.putExtra("choice", attachmentChoice); intent.putExtra("choice", attachmentChoice);
intent.putExtra("conversation",mSelectedConversation.getUuid());
startActivityForResult(intent, requestCode); startActivityForResult(intent, requestCode);
return true; return true;
} else { } else {

View File

@ -10,6 +10,8 @@ import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.util.Log;
import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.IdentityKey;
import java.util.ArrayList; import java.util.ArrayList;
@ -18,19 +20,21 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jid.Jid;
public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated { public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated {
private Jid accountJid;
private List<Jid> contactJids; private List<Jid> contactJids;
private Account mAccount; private Account mAccount;
private Conversation mConversation;
private TextView keyErrorMessage; private TextView keyErrorMessage;
private LinearLayout keyErrorMessageCard; private LinearLayout keyErrorMessageCard;
private TextView ownKeysTitle; private TextView ownKeysTitle;
@ -71,10 +75,6 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
protected void onCreate(final Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_trust_keys); setContentView(R.layout.activity_trust_keys);
try {
this.accountJid = Jid.fromString(getIntent().getExtras().getString(EXTRA_ACCOUNT));
} catch (final InvalidJidException ignored) {
}
this.contactJids = new ArrayList<>(); this.contactJids = new ArrayList<>();
for(String jid : getIntent().getStringArrayExtra("contacts")) { for(String jid : getIntent().getStringArrayExtra("contacts")) {
try { try {
@ -126,13 +126,15 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
synchronized (this.foreignKeysToTrust) { synchronized (this.foreignKeysToTrust) {
for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) { for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) {
hasForeignKeys = true;
final LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.keys_card, foreignKeys, false); final LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.keys_card, foreignKeys, false);
final Jid jid = entry.getKey();
final TextView header = (TextView) layout.findViewById(R.id.foreign_keys_title); final TextView header = (TextView) layout.findViewById(R.id.foreign_keys_title);
final LinearLayout keysContainer = (LinearLayout) layout.findViewById(R.id.foreign_keys_details); final LinearLayout keysContainer = (LinearLayout) layout.findViewById(R.id.foreign_keys_details);
header.setText(entry.getKey().toString()); final TextView informNoKeys = (TextView) layout.findViewById(R.id.no_keys_to_accept);
header.setText(jid.toString());
final Map<String, Boolean> fingerprints = entry.getValue(); final Map<String, Boolean> fingerprints = entry.getValue();
for (final String fingerprint : fingerprints.keySet()) { for (final String fingerprint : fingerprints.keySet()) {
hasForeignKeys = true;
addFingerprintRowWithListeners(keysContainer, mAccount, fingerprint, false, addFingerprintRowWithListeners(keysContainer, mAccount, fingerprint, false,
XmppAxolotlSession.Trust.fromBoolean(fingerprints.get(fingerprint)), false, XmppAxolotlSession.Trust.fromBoolean(fingerprints.get(fingerprint)), false,
new CompoundButton.OnCheckedChangeListener() { new CompoundButton.OnCheckedChangeListener() {
@ -146,11 +148,17 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
null null
); );
} }
if (fingerprints.size() == 0) {
informNoKeys.setVisibility(View.VISIBLE);
informNoKeys.setText(getString(R.string.no_keys_just_confirm,mAccount.getRoster().getContact(jid).getDisplayName()));
} else {
informNoKeys.setVisibility(View.GONE);
}
foreignKeys.addView(layout); foreignKeys.addView(layout);
} }
} }
ownKeysTitle.setText(accountJid.toString()); ownKeysTitle.setText(mAccount.getJid().toBareJid().toString());
ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE); ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE);
foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE); foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE);
if(hasPendingKeyFetches()) { if(hasPendingKeyFetches()) {
@ -176,6 +184,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
} }
private boolean reloadFingerprints() { private boolean reloadFingerprints() {
List<Jid> acceptedTargets = mConversation == null ? new ArrayList<Jid>() : mConversation.getAcceptedCryptoTargets();
ownKeysToTrust.clear(); ownKeysToTrust.clear();
AxolotlService service = this.mAccount.getAxolotlService(); AxolotlService service = this.mAccount.getAxolotlService();
Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED); Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED);
@ -197,7 +206,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
foreignFingerprints.put(identityKey.getFingerprint().replaceAll("\\s", ""), false); foreignFingerprints.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
} }
} }
if (foreignFingerprints.size() > 0) { if (foreignFingerprints.size() > 0 || !acceptedTargets.contains(jid)) {
foreignKeysToTrust.put(jid, foreignFingerprints); foreignKeysToTrust.put(jid, foreignFingerprints);
} }
} }
@ -207,11 +216,11 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
@Override @Override
public void onBackendConnected() { public void onBackendConnected() {
if (accountJid != null) { Intent intent = getIntent();
this.mAccount = xmppConnectionService.findAccountByJid(accountJid); this.mAccount = extractAccount(intent);
if (this.mAccount == null) { if (this.mAccount != null && intent != null) {
return; String uuid = intent.getStringExtra("conversation");
} this.mConversation = xmppConnectionService.findConversationByUuid(uuid);
reloadFingerprints(); reloadFingerprints();
populateView(); populateView();
} }
@ -276,8 +285,14 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
fingerprint, fingerprint,
XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint))); XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)));
} }
List<Jid> acceptedTargets = mConversation == null ? new ArrayList<Jid>() : mConversation.getAcceptedCryptoTargets();
synchronized (this.foreignKeysToTrust) { synchronized (this.foreignKeysToTrust) {
for (Map<String, Boolean> value : foreignKeysToTrust.values()) { for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) {
Jid jid = entry.getKey();
Map<String, Boolean> value = entry.getValue();
if (!acceptedTargets.contains(jid)) {
acceptedTargets.add(jid);
}
for (final String fingerprint : value.keySet()) { for (final String fingerprint : value.keySet()) {
mAccount.getAxolotlService().setFingerprintTrust( mAccount.getAxolotlService().setFingerprintTrust(
fingerprint, fingerprint,
@ -285,6 +300,11 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
} }
} }
} }
if (mConversation != null && mConversation.getMode() == Conversation.MODE_MULTI) {
Log.d(Config.LOGTAG,"commiting accepted crypto targets: "+acceptedTargets);
mConversation.setAcceptedCryptoTargets(acceptedTargets);
//xmppConnectionService.updateConversation(mConversation);
}
} }
private void unlock() { private void unlock() {

View File

@ -1,31 +1,39 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout android:id="@+id/foreign_keys_card"
android:id="@+id/foreign_keys_card" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin" android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin" android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginTop="@dimen/activity_vertical_margin"
android:background="@drawable/infocard_border" android:background="@drawable/infocard_border"
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/infocard_padding"> android:padding="@dimen/infocard_padding">
<TextView <TextView
android:id="@+id/foreign_keys_title" android:id="@+id/foreign_keys_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/black87" android:textColor="@color/black87"
android:textSize="?attr/TextSizeHeadline" android:textSize="?attr/TextSizeHeadline"
android:textStyle="bold"/> android:textStyle="bold"/>
<LinearLayout <LinearLayout
android:id="@+id/foreign_keys_details" android:id="@+id/foreign_keys_details"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:divider="?android:dividerHorizontal" android:divider="?android:dividerHorizontal"
android:showDividers="middle" android:orientation="vertical"
android:orientation="vertical"> android:showDividers="middle">
</LinearLayout> </LinearLayout>
<TextView
android:layout_marginTop="8dp"
android:id="@+id/no_keys_to_accept"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black87"
android:text="@string/no_keys_just_confirm"
android:textSize="?attr/TextSizeBody"/>
</LinearLayout> </LinearLayout>

View File

@ -599,4 +599,5 @@
<string name="this_field_is_required">This field is required</string> <string name="this_field_is_required">This field is required</string>
<string name="correct_message">Correct message</string> <string name="correct_message">Correct message</string>
<string name="send_corrected_message">Send corrected message</string> <string name="send_corrected_message">Send corrected message</string>
<string name="no_keys_just_confirm">You already trust this contact. By selecting \'done\' you are just confirming that %s is part of this conference.</string>
</resources> </resources>