From e4d9dca2fe6a75960d24f8af4dadba9a1a42845c Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Tue, 23 Dec 2014 17:19:00 -0500 Subject: Add ability to change password on server Fixes #260 --- .../eu/siacs/conversations/entities/Account.java | 45 ++++----- .../siacs/conversations/generator/IqGenerator.java | 12 ++- .../services/XmppConnectionService.java | 37 +++++-- .../conversations/ui/ConversationActivity.java | 2 +- .../conversations/ui/EditAccountActivity.java | 112 ++++++++++++--------- .../java/eu/siacs/conversations/utils/Xmlns.java | 1 + .../siacs/conversations/xmpp/XmppConnection.java | 4 + src/main/res/layout/activity_edit_account.xml | 11 +- src/main/res/values/strings.xml | 3 +- 9 files changed, 139 insertions(+), 88 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index d42974c6..b0cde62c 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -10,6 +10,7 @@ import net.java.otr4j.crypto.OtrCryptoException; import org.json.JSONException; import org.json.JSONObject; +import java.security.PublicKey; import java.security.interfaces.DSAPublicKey; import java.util.Collection; import java.util.List; @@ -152,7 +153,7 @@ public class Account extends AbstractEntity { this.avatar = avatar; } - public static Account fromCursor(Cursor cursor) { + public static Account fromCursor(final Cursor cursor) { Jid jid = null; try { jid = Jid.fromParts(cursor.getString(cursor.getColumnIndex(USERNAME)), @@ -168,11 +169,11 @@ public class Account extends AbstractEntity { cursor.getString(cursor.getColumnIndex(AVATAR))); } - public boolean isOptionSet(int option) { + public boolean isOptionSet(final int option) { return ((options & (1 << option)) != 0); } - public void setOption(int option, boolean value) { + public void setOption(final int option, final boolean value) { if (value) { this.options |= 1 << option; } else { @@ -243,34 +244,18 @@ public class Account extends AbstractEntity { return keys; } - public String getSSLFingerprint() { - if (keys.has("ssl_cert")) { - try { - return keys.getString("ssl_cert"); - } catch (JSONException e) { - return null; - } - } else { - return null; - } - } - - public void setSSLCertFingerprint(String fingerprint) { - this.setKey("ssl_cert", fingerprint); - } - - public boolean setKey(String keyName, String keyValue) { + public boolean setKey(final String keyName, final String keyValue) { try { this.keys.put(keyName, keyValue); return true; - } catch (JSONException e) { + } catch (final JSONException e) { return false; } } @Override public ContentValues getContentValues() { - ContentValues values = new ContentValues(); + final ContentValues values = new ContentValues(); values.put(UUID, uuid); values.put(USERNAME, jid.getLocalpart()); values.put(SERVER, jid.getDomainpart()); @@ -304,8 +289,8 @@ public class Account extends AbstractEntity { if (this.otrEngine == null) { return null; } - DSAPublicKey publicKey = (DSAPublicKey) this.otrEngine.getPublicKey(); - if (publicKey == null) { + final PublicKey publicKey = this.otrEngine.getPublicKey(); + if (publicKey == null || !(publicKey instanceof DSAPublicKey)) { return null; } this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey); @@ -338,7 +323,7 @@ public class Account extends AbstractEntity { if (keys.has("pgp_signature")) { try { return keys.getString("pgp_signature"); - } catch (JSONException e) { + } catch (final JSONException e) { return null; } } else { @@ -359,7 +344,7 @@ public class Account extends AbstractEntity { } public boolean hasBookmarkFor(final Jid conferenceJid) { - for (Bookmark bookmark : this.bookmarks) { + for (final Bookmark bookmark : this.bookmarks) { final Jid jid = bookmark.getJid(); if (jid != null && jid.equals(conferenceJid.toBareJid())) { return true; @@ -368,7 +353,7 @@ public class Account extends AbstractEntity { return false; } - public boolean setAvatar(String filename) { + public boolean setAvatar(final String filename) { if (this.avatar != null && this.avatar.equals(filename)) { return false; } else { @@ -395,7 +380,7 @@ public class Account extends AbstractEntity { } public String getShareableUri() { - String fingerprint = this.getOtrFingerprint(); + final String fingerprint = this.getOtrFingerprint(); if (fingerprint != null) { return "xmpp:" + this.getJid().toBareJid().toString() + "?otr-fingerprint="+fingerprint; } else { @@ -419,4 +404,8 @@ public class Account extends AbstractEntity { public void clearBlocklist() { getBlocklist().clear(); } + + public boolean isOnlineAndConnected() { + return this.getStatus() == State.ONLINE && this.getXmppConnection() != null; + } } diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 4b28e484..4bfc0963 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.Xmlns; @@ -117,7 +118,6 @@ public class IqGenerator extends AbstractGenerator { } return packet; } - public IqPacket generateGetBlockList() { final IqPacket iq = new IqPacket(IqPacket.TYPE_GET); iq.addChild("blocklist", Xmlns.BLOCKING); @@ -138,4 +138,14 @@ public class IqGenerator extends AbstractGenerator { block.addChild("item").setAttribute("jid", jid.toBareJid().toString()); return iq; } + + public IqPacket generateSetPassword(final Account account, final String newPassword) { + final IqPacket packet = new IqPacket(IqPacket.TYPE_SET); + packet.setTo(account.getServer()); + final Element query = packet.addChild("query", Xmlns.REGISTER); + final Jid jid = account.getJid(); + query.addChild("username").setContent(jid.isDomainJid() ? jid.toString() : jid.getLocalpart()); + query.addChild("password").setContent(newPassword); + return packet; + } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 6b0b960a..640074ba 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -222,7 +222,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private OnMucRosterUpdate mOnMucRosterUpdate = null; private int mucRosterChangedListenerCount = 0; private SecureRandom mRandom; - private FileObserver fileObserver = new FileObserver( + private final FileObserver fileObserver = new FileObserver( FileBackend.getConversationsImageDirectory()) { @Override @@ -232,7 +232,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } }; - private OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { + private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { @Override public void onJinglePacketReceived(Account account, JinglePacket packet) { @@ -246,7 +246,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private PendingIntent pendingPingIntent = null; private WakeLock wakeLock; private PowerManager pm; - private OnBindListener mOnBindListener = new OnBindListener() { + private final OnBindListener mOnBindListener = new OnBindListener() { @Override public void onBind(final Account account) { @@ -261,7 +261,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } }; - private OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { + private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { @Override public void onMessageAcknowledged(Account account, String uuid) { @@ -279,7 +279,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } }; private LruCache mBitmapCache; - private IqGenerator mIqGenerator = new IqGenerator(this); + private final IqGenerator mIqGenerator = new IqGenerator(this); private Thread mPhoneContactMergerThread; public PgpEngine getPgpEngine() { @@ -304,7 +304,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return this.mAvatarService; } - public void attachFileToConversation(Conversation conversation, final Uri uri, final UiCallback callback) { + public void attachFileToConversation(final Conversation conversation, + final Uri uri, + final UiCallback callback) { final Message message; if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) { message = new Message(conversation, "", @@ -1082,7 +1084,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public void createAccount(Account account) { + public void createAccount(final Account account) { account.initOtrEngine(this); databaseBackend.createAccount(account); this.accounts.add(account); @@ -1090,7 +1092,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa updateAccountUi(); } - public void updateAccount(Account account) { + public void updateAccount(final Account account) { this.statusListener.onStatusChanged(account); databaseBackend.updateAccount(account); reconnectAccount(account, false); @@ -1098,9 +1100,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa getNotificationService().updateErrorNotification(); } - public void deleteAccount(Account account) { + public void updateAccountPasswordOnServer(final Account account, final String newPassword) { + if (account.isOnlineAndConnected()) { + final IqPacket iq = getIqGenerator().generateSetPassword(account, newPassword); + sendIqPacket(account, iq, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE_RESULT) { + account.setPassword(newPassword); + updateAccount(account); + } + } + }); + } + } + + public void deleteAccount(final Account account) { synchronized (this.conversations) { - for (Conversation conversation : conversations) { + for (final Conversation conversation : conversations) { if (conversation.getAccount() == account) { if (conversation.getMode() == Conversation.MODE_MULTI) { leaveMuc(conversation); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index c41a18c6..5a9a208f 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -318,7 +318,7 @@ public class ConversationActivity extends XmppActivity menuUnblock.setVisible(false); } final Account account = this.getSelectedConversation().getAccount(); - if (account.getStatus() != Account.State.ONLINE || !account.getXmppConnection().getFeatures().blocking()) { + if (account.isOnlineAndConnected() || !account.getXmppConnection().getFeatures().blocking()) { menuBlock.setVisible(false); menuUnblock.setVisible(false); } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 9fce5ae5..4b6bea09 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -40,6 +40,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private EditText mPassword; private EditText mPasswordConfirm; private CheckBox mRegisterNew; + private CheckBox mChangePassword; private Button mCancelButton; private Button mSaveButton; private TableLayout mMoreTable; @@ -63,7 +64,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private boolean mFetchingAvatar = false; - private OnClickListener mSaveButtonClickListener = new OnClickListener() { + private final OnClickListener mSaveButtonClickListener = new OnClickListener() { @Override public void onClick(final View v) { @@ -74,6 +75,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate return; } final boolean registerNewAccount = mRegisterNew.isChecked(); + final boolean changePassword = mChangePassword.isChecked(); final Jid jid; try { jid = Jid.fromString(mAccountJid.getText().toString()); @@ -89,7 +91,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } final String password = mPassword.getText().toString(); final String passwordConfirm = mPasswordConfirm.getText().toString(); - if (registerNewAccount) { + if (registerNewAccount || changePassword) { if (!password.equals(passwordConfirm)) { mPasswordConfirm .setError(getString(R.string.passwords_do_not_match)); @@ -98,14 +100,22 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } } if (mAccount != null) { - mAccount.setPassword(password); try { mAccount.setUsername(jid.hasLocalpart() ? jid.getLocalpart() : ""); mAccount.setServer(jid.getDomainpart()); } catch (final InvalidJidException ignored) { } - mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); - xmppConnectionService.updateAccount(mAccount); + if (changePassword) { + if (mAccount.isOnlineAndConnected()) { + xmppConnectionService.updateAccountPasswordOnServer(mAccount, mPassword.getText().toString()); + } else { + mPassword.setError(getResources().getString(R.string.account_status_no_internet)); + } + } else { + mAccount.setPassword(password); + mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); + xmppConnectionService.updateAccount(mAccount); + } } else { try { if (xmppConnectionService.findAccountByJid(Jid.fromString(mAccountJid.getText().toString())) != null) { @@ -114,7 +124,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate mAccountJid.requestFocus(); return; } - } catch (InvalidJidException e) { + } catch (final InvalidJidException e) { return; } mAccount = new Account(jid.toBareJid(), password); @@ -132,10 +142,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } }; - private OnClickListener mCancelButtonClickListener = new OnClickListener() { + private final OnClickListener mCancelButtonClickListener = new OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { finish(); } }; @@ -167,47 +177,53 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } }); } - private UiCallback mAvatarFetchCallback = new UiCallback() { + private final UiCallback mAvatarFetchCallback = new UiCallback() { @Override - public void userInputRequried(PendingIntent pi, Avatar avatar) { + public void userInputRequried(final PendingIntent pi, final Avatar avatar) { finishInitialSetup(avatar); } @Override - public void success(Avatar avatar) { + public void success(final Avatar avatar) { finishInitialSetup(avatar); } @Override - public void error(int errorCode, Avatar avatar) { + public void error(final int errorCode, final Avatar avatar) { finishInitialSetup(avatar); } }; - private TextWatcher mTextWatcher = new TextWatcher() { + private final TextWatcher mTextWatcher = new TextWatcher() { @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { updateSaveButton(); } @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { } @Override - public void afterTextChanged(Editable s) { - + public void afterTextChanged(final Editable s) { + final boolean registrationReady = mAccount != null && + mAccount.isOnlineAndConnected() && + mAccount.getXmppConnection().getFeatures().register(); + if (jidToEdit != null && mAccount != null && registrationReady && + !mAccount.getPassword().equals(s.toString()) && !"".equals(s.toString())) { + mChangePassword.setVisibility(View.VISIBLE); + } else { + mChangePassword.setVisibility(View.INVISIBLE); + mChangePassword.setChecked(false); + } } }; - private OnClickListener mAvatarClickListener = new OnClickListener() { + private final OnClickListener mAvatarClickListener = new OnClickListener() { @Override - public void onClick(View view) { - if (mAccount!=null) { - Intent intent = new Intent(getApplicationContext(), + public void onClick(final View view) { + if (mAccount != null) { + final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); intent.putExtra("account", mAccount.getJid().toBareJid().toString()); startActivity(intent); @@ -220,7 +236,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override public void run() { - Intent intent; + final Intent intent; if (avatar != null) { intent = new Intent(getApplicationContext(), StartConversationActivity.class); @@ -251,8 +267,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mSaveButton.setEnabled(true); this.mSaveButton.setTextColor(getPrimaryTextColor()); if (jidToEdit != null) { - if (mAccount != null - && mAccount.getStatus() == Account.State.ONLINE) { + if (mAccount != null && mAccount.isOnlineAndConnected()) { this.mSaveButton.setText(R.string.save); if (!accountInfoEdited()) { this.mSaveButton.setEnabled(false); @@ -268,7 +283,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } protected boolean accountInfoEdited() { - return (!this.mAccount.getJid().toBareJid().equals( + return (!this.mAccount.getJid().toBareJid().toString().equals( this.mAccountJid.getText().toString())) || (!this.mAccount.getPassword().equals( this.mPassword.getText().toString())); @@ -284,7 +299,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_edit_account); this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid); @@ -295,6 +310,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mAvatar = (ImageView) findViewById(R.id.avater); this.mAvatar.setOnClickListener(this.mAvatarClickListener); this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new); + this.mChangePassword = (CheckBox) findViewById(R.id.account_change_password); this.mStats = (LinearLayout) findViewById(R.id.stats); this.mSessionEst = (TextView) findViewById(R.id.session_est); this.mServerInfoRosterVersion = (TextView) findViewById(R.id.server_info_roster_version); @@ -312,20 +328,20 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); this.mCancelButton.setOnClickListener(this.mCancelButtonClickListener); this.mMoreTable = (TableLayout) findViewById(R.id.server_info_more); - this.mRegisterNew - .setOnCheckedChangeListener(new OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(CompoundButton buttonView, - boolean isChecked) { - if (isChecked) { - mPasswordConfirm.setVisibility(View.VISIBLE); - } else { - mPasswordConfirm.setVisibility(View.GONE); - } - updateSaveButton(); + final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(final CompoundButton buttonView, + final boolean isChecked) { + if (isChecked) { + mPasswordConfirm.setVisibility(View.VISIBLE); + } else { + mPasswordConfirm.setVisibility(View.GONE); } - }); + updateSaveButton(); + } + }; + this.mRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword); + this.mChangePassword.setOnCheckedChangeListener(OnCheckedShowConfirmPassword); } @Override @@ -340,8 +356,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate showBlocklist.setVisible(false); showMoreInfo.setVisible(false); } else if (mAccount.getStatus() != Account.State.ONLINE) { - showBlocklist.setVisible(false); - showMoreInfo.setVisible(false); + showBlocklist.setVisible(false); + showMoreInfo.setVisible(false); } else if (!mAccount.getXmppConnection().getFeatures().blocking()) { showBlocklist.setVisible(false); } @@ -368,6 +384,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate getActionBar().setTitle(R.string.action_add_account); } } + this.mChangePassword.setVisibility(View.GONE); + this.mChangePassword.setChecked(false); } } @@ -415,11 +433,15 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) { this.mRegisterNew.setVisibility(View.VISIBLE); + this.mChangePassword.setVisibility(View.GONE); + this.mChangePassword.setChecked(false); this.mRegisterNew.setChecked(true); this.mPasswordConfirm.setText(this.mAccount.getPassword()); } else { this.mRegisterNew.setVisibility(View.GONE); this.mRegisterNew.setChecked(false); + this.mChangePassword.setVisibility(View.GONE); + this.mChangePassword.setChecked(false); } if (this.mAccount.getStatus() == Account.State.ONLINE && !this.mFetchingAvatar) { @@ -474,7 +496,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate .setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { if (copyTextToClipboard(fingerprint, R.string.otr_fingerprint)) { Toast.makeText( diff --git a/src/main/java/eu/siacs/conversations/utils/Xmlns.java b/src/main/java/eu/siacs/conversations/utils/Xmlns.java index acea2e56..67de7c79 100644 --- a/src/main/java/eu/siacs/conversations/utils/Xmlns.java +++ b/src/main/java/eu/siacs/conversations/utils/Xmlns.java @@ -3,4 +3,5 @@ package eu.siacs.conversations.utils; public final class Xmlns { public static final String BLOCKING = "urn:xmpp:blocking"; public static final String ROSTER = "jabber:iq:roster"; + public static final String REGISTER = "jabber:iq:register"; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 7fd0a4b6..b03d3f74 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -1082,6 +1082,10 @@ public class XmppConnection implements Runnable { return hasDiscoFeature(account.getServer(), Xmlns.BLOCKING); } + public boolean register() { + return hasDiscoFeature(account.getServer(), Xmlns.REGISTER); + } + public boolean sm() { return streamId != null; } diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml index a12766f9..cb626780 100644 --- a/src/main/res/layout/activity_edit_account.xml +++ b/src/main/res/layout/activity_edit_account.xml @@ -79,6 +79,15 @@ android:textColor="@color/primarytext" android:textSize="?attr/TextSizeBody" /> + + - - diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 29b81354..22e39a89 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -46,6 +46,7 @@ Contact blocked Would you like to remove %s as a bookmark? The conversation associated with this bookmark will not be removed. Register new account on server + Change password Share with Start Conversation Invite Contact @@ -249,7 +250,7 @@ to %s Send private message to %s Connect - This account does already exist + This account already exists Next Current session established Additional Information -- cgit v1.2.3