From 246694544c105d0388afb9894203a90e6faeb6d2 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 5 May 2020 20:19:19 +0300 Subject: [PATCH 01/70] Clean up unused file --- .../org/smartregister/util/DownloadForm.java | 102 ------------------ 1 file changed, 102 deletions(-) delete mode 100644 opensrp-app/src/main/java/org/smartregister/util/DownloadForm.java diff --git a/opensrp-app/src/main/java/org/smartregister/util/DownloadForm.java b/opensrp-app/src/main/java/org/smartregister/util/DownloadForm.java deleted file mode 100644 index c19ad4432..000000000 --- a/opensrp-app/src/main/java/org/smartregister/util/DownloadForm.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.smartregister.util; - -import android.util.Log; - -import org.apache.http.util.ByteArrayBuffer; -import org.smartregister.domain.DownloadStatus; -import org.smartregister.domain.Response; -import org.smartregister.domain.ResponseStatus; -import org.smartregister.service.FormPathService; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; - -/** - * Created by Dimas Ciputra on 3/21/15. - */ -public class DownloadForm { - - /** - * @author Rodgers Andati - * @since 2019-04-25 - * This method downloads images given url. Migration from the old method that used httpclient - * @param downloadURL This is the url of the image - * @param fileName This is how the image should be name after it has been downloaded. - * @param username This is the username used to authenticate when accessing the url endpoint. - * @param password This is the password used to authenticate when accessing the url endpoint. - * @return Response This returns whether the download succeeded or failed. - */ - public static Response downloadFromURL(String downloadURL, String fileName, String username, String password) { - HttpURLConnection httpUrlConnection; - try { - File dir = new File(FormPathService.sdcardPathDownload); - - if (!dir.exists()) { - dir.mkdirs(); - } - - File file = new File(dir, fileName); - - long startTime = System.currentTimeMillis(); - Log.d("DownloadFormService", "download begin"); - Log.d("DownloadFormService", "download url: " + downloadURL); - Log.d("DownloadFormService", "download file name: " + fileName); - - /* Open connection to URL */ - URL url = new URL(downloadURL); - httpUrlConnection = (HttpURLConnection) url.openConnection(); - - httpUrlConnection.setRequestProperty("username", username); - httpUrlConnection.setRequestProperty("password", password); - - httpUrlConnection.connect(); - - int status = httpUrlConnection.getResponseCode(); - if (status == HttpURLConnection.HTTP_OK) { - - InputStream inputStream = httpUrlConnection.getInputStream(); - BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); - - long fileLength = bufferedInputStream.available(); - if (fileLength == 0) { - return new Response(ResponseStatus.success, - DownloadStatus.nothingDownloaded); - } - Log.d("DownloadFormService", "file length : " + fileLength); - - ByteArrayBuffer baf = new ByteArrayBuffer(9999); - int current = 0; - while ((current = bufferedInputStream.read()) != -1) { - baf.append((byte) current); - } - - /* Convert the bytes to String */ - FileOutputStream fos = new FileOutputStream(file); - fos.write(baf.toByteArray()); - fos.flush(); - fos.close(); - - Log.d("DownloadFormService", - "download finished in " + ((System.currentTimeMillis() - startTime) / 1000) - + " sec"); - httpUrlConnection.disconnect(); - - } else { - Log.d("RESPONSE", "Server returned non-OK status: " + status); - return new Response(ResponseStatus.failure, DownloadStatus.failedDownloaded); - } - - } catch (IOException e) { - Log.d("DownloadFormService", "download error : " + e); - return new Response(ResponseStatus.success, DownloadStatus.failedDownloaded); - } - - return new Response(ResponseStatus.success, DownloadStatus.downloaded); - } - -} \ No newline at end of file From abd54bd799f0f0876f03a251c07bd84ff808ed0d Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 5 May 2020 20:21:19 +0300 Subject: [PATCH 02/70] Implement account authenticator --- .../account/AbstractAccountAuthenticator.java | 14 ++ .../account/AccountAuthenticator.java | 122 ++++++++++++++++++ .../account/AccountAuthenticatorXml.java | 34 +++++ 3 files changed, 170 insertions(+) create mode 100644 opensrp-app/src/main/java/org/smartregister/account/AbstractAccountAuthenticator.java create mode 100644 opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java create mode 100644 opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticatorXml.java diff --git a/opensrp-app/src/main/java/org/smartregister/account/AbstractAccountAuthenticator.java b/opensrp-app/src/main/java/org/smartregister/account/AbstractAccountAuthenticator.java new file mode 100644 index 000000000..5497ef7ba --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/account/AbstractAccountAuthenticator.java @@ -0,0 +1,14 @@ +package org.smartregister.account; + +import android.content.Context; + +/** + * Created by ndegwamartin on 11/05/2020. + */ +public abstract class AbstractAccountAuthenticator extends android.accounts.AbstractAccountAuthenticator { + public AbstractAccountAuthenticator(Context context) { + super(context); + } + + public abstract String getRefreshToken(); +} diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java new file mode 100644 index 000000000..48d8158ac --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java @@ -0,0 +1,122 @@ +package org.smartregister.account; + +import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.AccountManager; +import android.accounts.NetworkErrorException; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.text.TextUtils; + +import org.smartregister.CoreLibrary; +import org.smartregister.view.activity.LoginActivity; + +import timber.log.Timber; + +/** + * Created by ndegwamartin on 2020-04-27. + */ +public class AccountAuthenticator extends AbstractAccountAuthenticator { + + private String TAG = AccountAuthenticator.class.getCanonicalName(); + private final Context mContext; + + public AccountAuthenticator(Context context) { + super(context); + + this.mContext = context; + } + + @Override + public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { + + final Intent intent = new Intent(mContext, LoginActivity.class); + intent.putExtra(AccountHelper.INTENT_KEY.ACCOUNT_TYPE, accountType); + intent.putExtra(AccountHelper.INTENT_KEY.AUTH_TYPE, authTokenType); + intent.putExtra(AccountHelper.INTENT_KEY.IS_NEW_ACCOUNT, true); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { + + Timber.d("getAuthToken"); + + AccountManager accountManager = CoreLibrary.getInstance().getAccountManager(); + + String authToken = accountManager.peekAuthToken(account, authTokenType); + String refreshToken = ""; + Timber.d("peekAuthToken " + authToken); + + if (TextUtils.isEmpty(authToken)) { + final String password = accountManager.getPassword(account); + if (password != null) { + try { + + Timber.d("Authenticate with saved credentials"); + + AccountResponse accountResponse = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticate(account.name, password, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD); + authToken = accountResponse.getAccessToken(); + refreshToken = accountResponse.getRefreshToken(); + + } catch (Exception e) { + Timber.e(e); + } + } + } + + if (!TextUtils.isEmpty(authToken)) { + final Bundle result = new Bundle(); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); + result.putString(AccountManager.KEY_AUTHTOKEN, authToken); + result.putString(AccountHelper.KEY_REFRESH_TOKEN, refreshToken); + return result; + } + + final Intent intent = new Intent(mContext, LoginActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + intent.putExtra(AccountHelper.INTENT_KEY.ACCOUNT_TYPE, account.type); + intent.putExtra(AccountHelper.INTENT_KEY.AUTH_TYPE, authTokenType); + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + @Override + public String getAuthTokenLabel(String authTokenType) { + return authTokenType.toUpperCase(); + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { + final Bundle result = new Bundle(); + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); + return result; + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { + return null; + } + + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { + return null; + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { + return null; + } + + @Override + public String getRefreshToken() { + return AccountHelper.getAccountManagerValue(AccountHelper.KEY_REFRESH_TOKEN,CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); + } +} diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticatorXml.java b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticatorXml.java new file mode 100644 index 000000000..f6a2956ed --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticatorXml.java @@ -0,0 +1,34 @@ +package org.smartregister.account; + +/** + * Created by ndegwamartin on 08/05/2020. + */ +public class AccountAuthenticatorXml { + private String accountType; + private String accountName; + private int icon; + + public String getAccountType() { + return accountType; + } + + public String getAccountName() { + return accountName; + } + + public int getIcon() { + return icon; + } + + public void setAccountType(String accountType) { + this.accountType = accountType; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public void setIcon(int icon) { + this.icon = icon; + } +} From 75e54a7981fafd8adc12268dd452d9fef40b2740 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Wed, 6 May 2020 16:21:00 +0300 Subject: [PATCH 03/70] Implement account service and helpers --- .../smartregister/account/AccountError.java | 18 +++++ .../smartregister/account/AccountHelper.java | 74 +++++++++++++++++++ .../account/AccountResponse.java | 62 ++++++++++++++++ .../smartregister/account/AccountService.java | 19 +++++ 4 files changed, 173 insertions(+) create mode 100644 opensrp-app/src/main/java/org/smartregister/account/AccountError.java create mode 100644 opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java create mode 100644 opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java create mode 100644 opensrp-app/src/main/java/org/smartregister/account/AccountService.java diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountError.java b/opensrp-app/src/main/java/org/smartregister/account/AccountError.java new file mode 100644 index 000000000..87cefcd8a --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountError.java @@ -0,0 +1,18 @@ +package org.smartregister.account; + +import java.io.Serializable; + +/** + * Created by ndegwamartin on 2020-04-27. + */ +public class AccountError implements Serializable { + + private int statusCode; + private String error; + + public AccountError(int statusCode, String error) { + this.statusCode = statusCode; + this.error = error; + + } +} diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java new file mode 100644 index 000000000..df0dda4bb --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java @@ -0,0 +1,74 @@ +package org.smartregister.account; + +import android.accounts.Account; +import android.accounts.AccountManager; + +import org.smartregister.CoreLibrary; + +/** + * Created by ndegwamartin on 2020-04-27. + */ +public class AccountHelper { + + private static AccountManager accountManager = CoreLibrary.getInstance().getAccountManager(); + + public final static String KEY_REFRESH_TOKEN = "KEY_REFRESH_TOKEN"; + public final static int MAX_AUTH_RETRIES = 1; + + public static final class OAUTH { + public final static String TOKEN_ENDPOINT = "/oauth/token"; + + public static final class GRANT_TYPE { + public final static String PASSWORD = "password"; + public final static String REFRESH_TOKEN = "refresh_token"; + + } + } + + public static final class INTENT_KEY { + + public final static String ACCOUNT_TYPE = "ACCOUNT_TYPE"; + public final static String AUTH_TYPE = "AUTH_TYPE"; + public final static String ACCOUNT_NAME = "ACCOUNT_NAME"; + public static final String ERROR_MESSAGE = "ERROR_MESSAGE"; + public final static String ACCOUNT_PASSWORD = "ACCOUNT_PASSWORD"; + public final static String IS_NEW_ACCOUNT = "IS_NEW_ACCOUNT"; + public final static String ACCOUNT_GROUP_ID = "ACCOUNT_GROUP_ID"; + } + + public static final class TOKEN_TYPE { + public final static String PROVIDER = "provider"; + public final static String ADMIN = "admin"; + } + + + public static Account getOauthAccountByType(String accountType) { + Account[] accounts = accountManager.getAccountsByType(accountType); + if (accounts.length > 0) { + Account account = accounts[0]; + return account; + } + return null; + } + + public static String getAccountManagerValue(String key, String accountType) { + Account account = AccountHelper.getOauthAccountByType(accountType); + if (account != null) { + return CoreLibrary.getInstance().getAccountManager().getUserData(account, key); + } + return null; + } + + /** + * @param accountType unique name to identify our account type in the Account Manager + * @param authTokenType type of token requested from server e.g. PROVIDER, ADMIN + * @return + */ + public static String getOAuthToken(String accountType, String authTokenType) { + Account account = getOauthAccountByType(accountType); + + return accountManager.peekAuthToken(account, authTokenType); + } + + +} diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java b/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java new file mode 100644 index 000000000..0ece15cfc --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java @@ -0,0 +1,62 @@ +package org.smartregister.account; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by ndegwamartin on 06/05/2020. + */ +public class AccountResponse { + + @SerializedName("access_token") + private String accessToken; + + @SerializedName("token_type") + private String tokenType; + + @SerializedName("refresh_token") + private String refreshToken; + + + @SerializedName("expires_in") + private Integer ExpiresIn; + + @SerializedName("scope") + private String Scope; + + private int status; + + private AccountError accountError; + + public AccountResponse(int status, AccountError accountError) { + this.status = status; + this.accountError = accountError; + } + + public int getStatus() { + return status; + } + + public AccountError getAccountError() { + return accountError; + } + + public String getAccessToken() { + return accessToken; + } + + public String getTokenType() { + return tokenType; + } + + public String getRefreshToken() { + return refreshToken; + } + + public Integer getExpiresIn() { + return ExpiresIn; + } + + public String getScope() { + return Scope; + } +} diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountService.java b/opensrp-app/src/main/java/org/smartregister/account/AccountService.java new file mode 100644 index 000000000..2056fc9c8 --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountService.java @@ -0,0 +1,19 @@ +package org.smartregister.account; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +/** + * Created by ndegwamartin on 2020-04-27. + */ +public class AccountService extends Service { + + @Override + public IBinder onBind(Intent intent) { + + AccountAuthenticator authenticator = new AccountAuthenticator(this); + return authenticator.getIBinder(); + + } +} From dd3d2da96e01cd97760f3a441e415e7ce6c7175c Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Thu, 7 May 2020 19:10:30 +0300 Subject: [PATCH 04/70] Migrate login Migrate login to oauth2 Implement configuration helpers Update Manifest and properties configs --- gradle.properties | 3 +- opensrp-app/AndroidManifest.xml | 68 +++++++++++------- opensrp-app/res/xml/authenticator.xml | 7 ++ .../java/org/smartregister/CoreLibrary.java | 22 ++++++ .../org/smartregister/SyncConfiguration.java | 18 ++++- .../login/interactor/BaseLoginInteractor.java | 13 ++-- .../login/task/RemoteLoginTask.java | 71 ++++++++++++++----- .../view/activity/BaseLoginActivity.java | 18 ++++- .../view/activity/LoginActivity.java | 4 +- .../view/contract/BaseLoginContract.java | 4 ++ 10 files changed, 174 insertions(+), 54 deletions(-) create mode 100644 opensrp-app/res/xml/authenticator.xml diff --git a/gradle.properties b/gradle.properties index ae4fbfebe..ac928efda 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=1.15.4-SNAPSHOT +VERSION_NAME=2.0.4-SNAPSHOT VERSION_CODE=1 GROUP=org.smartregister POM_SETTING_DESCRIPTION=OpenSRP Client Core Application @@ -12,5 +12,4 @@ POM_SETTING_LICENCE_DIST=repo POM_SETTING_DEVELOPER_ID=opensrp POM_SETTING_DEVELOPER_NAME=OpenSRP Onadev - org.gradle.jvmargs=-Xmx2048m diff --git a/opensrp-app/AndroidManifest.xml b/opensrp-app/AndroidManifest.xml index 7431d16d6..4428271db 100644 --- a/opensrp-app/AndroidManifest.xml +++ b/opensrp-app/AndroidManifest.xml @@ -1,68 +1,82 @@ + android:versionName="3.0.1"> - + + + + + android:name=".service.ImageUploadSyncService" + android:enabled="true" /> + + + + + + + + android:screenOrientation="landscape" + android:theme="@style/AppThemeNoTitle" /> + android:screenOrientation="landscape" + android:theme="@style/AppThemeNoTitle" /> + android:screenOrientation="landscape" + android:theme="@style/AppThemeNoTitle" /> + android:screenOrientation="landscape" + android:theme="@style/AppThemeNoTitle" /> + android:screenOrientation="landscape" + android:theme="@style/AppThemeNoTitle" /> + android:screenOrientation="landscape" + android:theme="@style/AppThemeNoTitle" /> + android:screenOrientation="landscape" + android:theme="@android:style/Theme.Holo.NoActionBar" /> + android:screenOrientation="portrait" + android:theme="@style/AppThemeNoTitle" /> + android:screenOrientation="landscape" + android:theme="@android:style/Theme.Holo.Dialog.NoActionBar" /> - + - + diff --git a/opensrp-app/res/xml/authenticator.xml b/opensrp-app/res/xml/authenticator.xml new file mode 100644 index 000000000..1df47b99b --- /dev/null +++ b/opensrp-app/res/xml/authenticator.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java b/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java index 7bdfc3af5..fdc53fcc9 100644 --- a/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java +++ b/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java @@ -1,11 +1,13 @@ package org.smartregister; +import android.accounts.AccountManager; import android.content.Intent; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; +import org.smartregister.account.AccountAuthenticatorXml; import org.smartregister.authorizer.P2PSyncAuthorizationService; import org.smartregister.p2p.P2PLibrary; import org.smartregister.pathevaluator.PathEvaluatorLibrary; @@ -13,6 +15,7 @@ import org.smartregister.repository.P2PReceiverTransferDao; import org.smartregister.repository.P2PSenderTransferDao; import org.smartregister.sync.P2PSyncFinishCallback; +import org.smartregister.util.Utils; import static android.preference.PreferenceManager.getDefaultSharedPreferences; @@ -34,6 +37,10 @@ public class CoreLibrary { private P2POptions p2POptions; + private AccountManager accountManager; + + private AccountAuthenticatorXml authenticatorXml; + public static void init(Context context) { init(context, null); } @@ -151,6 +158,20 @@ public SyncConfiguration getSyncConfiguration() { return syncConfiguration; } + public AccountManager getAccountManager() { + if (accountManager == null) + accountManager = AccountManager.get(context.applicationContext()); + + return accountManager; + } + + public AccountAuthenticatorXml getAccountAuthenticatorXml() { + if (authenticatorXml == null) + authenticatorXml = Utils.parseAuthenticatorXMLConfigData(context.applicationContext()); + + return authenticatorXml; + } + public static long getBuildTimeStamp() { return buildTimeStamp; } @@ -186,4 +207,5 @@ private void sendPeerToPeerProcessingStatus(boolean status) { LocalBroadcastManager.getInstance(context.applicationContext()) .sendBroadcast(intent); } + } diff --git a/opensrp-app/src/main/java/org/smartregister/SyncConfiguration.java b/opensrp-app/src/main/java/org/smartregister/SyncConfiguration.java index 9385ff80c..290404ad9 100644 --- a/opensrp-app/src/main/java/org/smartregister/SyncConfiguration.java +++ b/opensrp-app/src/main/java/org/smartregister/SyncConfiguration.java @@ -1,5 +1,7 @@ package org.smartregister; +import org.smartregister.account.AccountHelper; + import java.util.ArrayList; import java.util.List; @@ -29,7 +31,9 @@ public boolean disableActionService() { return false; } - // determines whether to sync settings from server side. return false if not + /** + * Determines whether to sync settings from server side. return false if not + */ public boolean isSyncSettings() { return false; } @@ -165,4 +169,16 @@ public boolean clearDataOnNewTeamLogin() { public boolean runPlanEvaluationOnClientProcessing() { return false; } + + public abstract String getOauthClientId(); + + public abstract String getOauthClientSecret(); + + /** + * Returns number of times to retry if 401 is received on a request before forcing user to enter credentials + * Default is once, can be overriden + */ + public int getMaxAuthenticationRetries() { + return AccountHelper.MAX_AUTH_RETRIES; + } } diff --git a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java index 0a512ff23..e59d97d3e 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java +++ b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java @@ -12,6 +12,7 @@ import org.smartregister.CoreLibrary; import org.smartregister.P2POptions; import org.smartregister.R; +import org.smartregister.account.AccountAuthenticatorXml; import org.smartregister.domain.LoginResponse; import org.smartregister.domain.TimeStatus; import org.smartregister.event.Listener; @@ -72,7 +73,7 @@ public void loginWithLocalFlag(WeakReference view, boole if (localLogin) { localLogin(view, userName, password); } else { - remoteLogin(userName, password); + remoteLogin(userName, password, CoreLibrary.getInstance().getAccountAuthenticatorXml()); } Timber.i("Login result finished " + DateTime.now().toString()); @@ -115,19 +116,19 @@ public void run() { }).start(); } - private void remoteLogin(final String userName, final String password) { + private void remoteLogin(final String userName, final String password, final AccountAuthenticatorXml accountAuthenticatorXml) { try { if (getSharedPreferences().fetchBaseURL("").isEmpty() && StringUtils.isNotBlank(this.getApplicationContext().getString(R.string.opensrp_url))) { getSharedPreferences().savePreference("DRISHTI_BASE_URL", getApplicationContext().getString(R.string.opensrp_url)); } if (!getSharedPreferences().fetchBaseURL("").isEmpty()) { - tryRemoteLogin(userName, password, new Listener() { + tryRemoteLogin(userName, password, accountAuthenticatorXml, new Listener() { public void onEvent(LoginResponse loginResponse) { getLoginView().enableLoginButton(true); if (loginResponse == LoginResponse.SUCCESS) { - String username=loginResponse.payload()!=null && loginResponse.payload().user != null && StringUtils.isNotBlank(loginResponse.payload().user.getUsername()) + String username = loginResponse.payload() != null && loginResponse.payload().user != null && StringUtils.isNotBlank(loginResponse.payload().user.getUsername()) ? loginResponse.payload().user.getUsername() : userName; if (getUserService().isUserInPioneerGroup(username)) { TimeStatus timeStatus = getUserService().validateDeviceTime( @@ -198,11 +199,11 @@ public void onComplete() { } } - private void tryRemoteLogin(final String userName, final String password, final Listener afterLogincheck) { + private void tryRemoteLogin(final String userName, final String password, final AccountAuthenticatorXml accountAuthenticatorXml, final Listener afterLogincheck) { if (remoteLoginTask != null && !remoteLoginTask.isCancelled()) { remoteLoginTask.cancel(true); } - remoteLoginTask = new RemoteLoginTask(getLoginView(), userName, password, afterLogincheck); + remoteLoginTask = new RemoteLoginTask(getLoginView(), userName, password, accountAuthenticatorXml, afterLogincheck); remoteLoginTask.execute(); } diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index fbcfc5967..4dc1580ea 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -1,6 +1,9 @@ package org.smartregister.login.task; +import android.accounts.Account; +import android.accounts.AccountManager; import android.os.AsyncTask; +import android.os.Bundle; import org.json.JSONArray; import org.json.JSONException; @@ -9,6 +12,9 @@ import org.smartregister.Context; import org.smartregister.CoreLibrary; import org.smartregister.R; +import org.smartregister.account.AccountAuthenticatorXml; +import org.smartregister.account.AccountHelper; +import org.smartregister.account.AccountResponse; import org.smartregister.domain.LoginResponse; import org.smartregister.event.Listener; import org.smartregister.sync.helper.SyncSettingsServiceHelper; @@ -17,6 +23,8 @@ import timber.log.Timber; +import static org.smartregister.domain.LoginResponse.CUSTOM_SERVER_RESPONSE; + /** * Created by ndegwamartin on 22/06/2018. */ @@ -25,13 +33,15 @@ public class RemoteLoginTask extends AsyncTask { private BaseLoginContract.View mLoginView; private final String mUsername; private final String mPassword; + private final AccountAuthenticatorXml mAccountAuthenticatorXml; private final Listener afterLoginCheck; - public RemoteLoginTask(BaseLoginContract.View loginView, String username, String password, Listener afterLoginCheck) { + public RemoteLoginTask(BaseLoginContract.View loginView, String username, String password, AccountAuthenticatorXml accountAuthenticatorXml, Listener afterLoginCheck) { mLoginView = loginView; mUsername = username; mPassword = password; + mAccountAuthenticatorXml = accountAuthenticatorXml; this.afterLoginCheck = afterLoginCheck; } @@ -43,23 +53,52 @@ protected void onPreExecute() { @Override protected LoginResponse doInBackground(Void... params) { - LoginResponse loginResponse = getOpenSRPContext().userService().isValidRemoteLogin(mUsername, mPassword); - if (loginResponse != null && loginResponse.equals(LoginResponse.SUCCESS) && getOpenSRPContext().userService().getGroupId(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { - publishProgress(R.string.loading_client_settings); - - SyncSettingsServiceHelper syncSettingsServiceHelper = new SyncSettingsServiceHelper(getOpenSRPContext().configuration().dristhiBaseURL(), getOpenSRPContext().getHttpAgent()); - syncSettingsServiceHelper.setUsername(mUsername); - syncSettingsServiceHelper.setPassword(mPassword); - - try { - JSONArray settings = pullSetting(syncSettingsServiceHelper, loginResponse); - JSONObject data = new JSONObject(); - data.put(AllConstants.PREF_KEY.SETTINGS, settings); - loginResponse.setRawData(data); - } catch (JSONException e) { - Timber.e(e); + + LoginResponse loginResponse; + Bundle userData = null; + try { + + AccountResponse response = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticate(mUsername, mPassword, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD); + + AccountManager mAccountManager = CoreLibrary.getInstance().getAccountManager(); + + final Account account = new Account(mUsername, mAccountAuthenticatorXml.getAccountType()); + + loginResponse = getOpenSRPContext().userService().fetchUserDetails(response.getAccessToken()); + + if (loginResponse != null && loginResponse.equals(LoginResponse.SUCCESS)) { + + userData = getOpenSRPContext().userService().saveUserGroup(mUsername, mPassword, loginResponse.payload()); + + if (getOpenSRPContext().userService().getGroupId(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { + + + publishProgress(R.string.loading_client_settings); + + + SyncSettingsServiceHelper syncSettingsServiceHelper = new SyncSettingsServiceHelper(getOpenSRPContext().configuration().dristhiBaseURL(), getOpenSRPContext().getHttpAgent()); + try { + JSONArray settings = pullSetting(syncSettingsServiceHelper, loginResponse); + JSONObject data = new JSONObject(); + data.put(AllConstants.PREF_KEY.SETTINGS, settings); + loginResponse.setRawData(data); + } catch (JSONException e) { + Timber.e(e); + } + + } } + + mAccountManager.addAccountExplicitly(account, response.getAccessToken(), userData); + mAccountManager.setAuthToken(account, mLoginView.getAuthTokenType(), response.getAccessToken()); + mAccountManager.setUserData(account, AccountHelper.KEY_REFRESH_TOKEN, response.getRefreshToken()); + + + } catch (Exception e) { + + loginResponse = CUSTOM_SERVER_RESPONSE.withMessage(e.getMessage()); } + return loginResponse; } diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java index e04d13d67..a2754f541 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java @@ -27,6 +27,7 @@ import org.joda.time.DateTime; import org.smartregister.R; +import org.smartregister.account.AccountHelper; import org.smartregister.util.SyncUtils; import org.smartregister.util.Utils; import org.smartregister.view.contract.BaseLoginContract; @@ -49,6 +50,7 @@ public abstract class BaseLoginActivity extends MultiLanguageActivity implements private Button loginButton; private Boolean showPasswordChecked = false; private SyncUtils syncUtils; + private String authTokenType; @Override protected void onCreate(Bundle savedInstanceState) { @@ -272,7 +274,7 @@ public boolean isAppVersionAllowed() { } catch (PackageManager.NameNotFoundException e) { Timber.e(e); } - return isAppVersionAllowed; + return isAppVersionAllowed; } @Override @@ -287,4 +289,18 @@ public void showClearDataDialog(@NonNull DialogInterface.OnClickListener onClick .setCancelable(false) .show(); } + + public String getAuthTokenType() { + + authTokenType = getIntent().getStringExtra(AccountHelper.INTENT_KEY.AUTH_TYPE); + + if (authTokenType == null) + authTokenType = AccountHelper.TOKEN_TYPE.PROVIDER; + + return authTokenType; + } + + public boolean isNewAccount(){ + return getIntent().getBooleanExtra(AccountHelper.INTENT_KEY.IS_NEW_ACCOUNT, false); + } } \ No newline at end of file diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java index a38c38d78..a676faae7 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java @@ -1,5 +1,6 @@ package org.smartregister.view.activity; +import android.accounts.AccountAuthenticatorActivity; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; @@ -171,7 +172,6 @@ private void localLogin(View view, String userName, String password) { } - private void remoteLogin(final View view, final String userName, final String password) { try { @@ -267,7 +267,7 @@ private void remoteLoginWith(String userName, String password, LoginResponseData DrishtiSyncScheduler.startOnlyIfConnectedToNetwork(getApplicationContext()); } - private void goToHome() { + protected void goToHome() { startActivity(new Intent(this, NativeHomeActivity.class)); finish(); } diff --git a/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java b/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java index e52fa9096..a020d8e52 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java +++ b/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java @@ -59,6 +59,10 @@ interface View { boolean isAppVersionAllowed(); void showClearDataDialog(@NonNull DialogInterface.OnClickListener onClickListener); + + String getAuthTokenType(); + + boolean isNewAccount(); } interface Interactor { From c0b858934a0bc165fec2575bf7d67c4755a11104 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Fri, 8 May 2020 18:50:23 +0300 Subject: [PATCH 05/70] Migrate user service Clean up update utils --- .../smartregister/service/UserService.java | 50 ++++++++++++++++--- .../helper/SyncSettingsServiceHelper.java | 5 ++ .../org/smartregister/util/SyncUtils.java | 40 ++------------- .../java/org/smartregister/util/Utils.java | 49 +++++++++++++++++- 4 files changed, 99 insertions(+), 45 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index d14b9d5ac..6ec940e33 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -2,6 +2,7 @@ import android.annotation.TargetApi; import android.os.Build; +import android.os.Bundle; import android.security.KeyPairGeneratorSpec; import android.support.annotation.VisibleForTesting; import android.util.Base64; @@ -11,6 +12,7 @@ import org.smartregister.DristhiConfiguration; import org.smartregister.SyncConfiguration; import org.smartregister.SyncFilter; +import org.smartregister.account.AccountHelper; import org.smartregister.domain.LoginResponse; import org.smartregister.domain.Response; import org.smartregister.domain.TimeStatus; @@ -291,6 +293,31 @@ public boolean isUserInPioneerGroup(String userName) { return false; } + public LoginResponse oauth2Authenticate(String userName, String password) { + String requestURL; + + requestURL = configuration.dristhiBaseURL() + OPENSRP_AUTH_USER_URL_PATH; + + LoginResponse loginResponse = httpAgent + .urlCanBeAccessWithGivenCredentials(requestURL, userName, password); + + if (loginResponse != null && loginResponse.equals(LoginResponse.SUCCESS)) { + saveUserGroup(userName, password, loginResponse.payload()); + } + + return loginResponse; + } + + public LoginResponse fetchUserDetails(String accessToken) { + String requestURL; + + requestURL = configuration.dristhiBaseURL() + OPENSRP_AUTH_USER_URL_PATH; + + LoginResponse loginResponse = httpAgent.fetchUserDetails(requestURL, accessToken); + + return loginResponse; + } + public LoginResponse isValidRemoteLogin(String userName, String password) { String requestURL; @@ -337,7 +364,7 @@ private boolean loginWith(String userName, String password) { username = allSharedPreferences.fetchRegisteredANM(); allSharedPreferences.updateANMUserName(username); DrishtiApplication.getInstance().getRepository().getReadableDatabase(); - allSettings.registerANM(username, password); + allSettings.registerANM(username); return loginSuccessful; } @@ -545,15 +572,19 @@ public void saveUserInfo(User user) { * @param userInfo The user's info from the * endpoint (should contain the {team}.{team}.{uuid} key) */ - public void saveUserGroup(String userName, String password, LoginResponseData userInfo) { - String username = userInfo.user != null && StringUtils.isNotBlank(userInfo.user.getUsername()) - ? userInfo.user.getUsername() : userName; + public Bundle saveUserGroup(String userName, String password, LoginResponseData userInfo) { + Bundle bundle = new Bundle(); + + String username = userInfo.user != null && StringUtils.isNotBlank(userInfo.user.getUsername()) ? userInfo.user.getUsername() : userName; + bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_NAME, username); + + if (keyStore != null && username != null) { try { KeyStore.PrivateKeyEntry privateKeyEntry = createUserKeyPair(username); if (password == null) { - return; + return null; } @@ -572,7 +603,10 @@ public void saveUserGroup(String userName, String password, LoginResponseData us } if (StringUtils.isBlank(groupId)) { - return; + return null; + } else { + + bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, groupId); } if (privateKeyEntry != null) { @@ -593,6 +627,8 @@ public void saveUserGroup(String userName, String password, LoginResponseData us Timber.e(e); } } + + return bundle; } public boolean hasARegisteredUser() { @@ -601,7 +637,7 @@ public boolean hasARegisteredUser() { public void logout() { logoutSession(); - allSettings.registerANM("", ""); + allSettings.registerANM(""); allSettings.savePreviousFetchIndex("0"); DrishtiApplication.getInstance().getRepository().deleteRepository(); } diff --git a/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java b/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java index 4c8b49057..293a7cc93 100644 --- a/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java @@ -103,8 +103,12 @@ private void getGlobalSettings(JSONArray settings) throws JSONException { globalSettings = pullGlobalSettingsFromServer(); } +<<<<<<< HEAD aggregateSettings(settings, globalSettings); } +======= + Response resp = httpAgent.fetchWithCredentials(url); +>>>>>>> Migrate user service private void aggregateSettings(JSONArray settings, JSONArray globalSettings) throws JSONException { if (!JsonFormUtils.isBlankJsonArray(globalSettings)) { @@ -229,5 +233,6 @@ public String getPassword() { public void setPassword(String password) { this.password = password; } + } diff --git a/opensrp-app/src/main/java/org/smartregister/util/SyncUtils.java b/opensrp-app/src/main/java/org/smartregister/util/SyncUtils.java index d0807c2fc..5a31f61c4 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/SyncUtils.java +++ b/opensrp-app/src/main/java/org/smartregister/util/SyncUtils.java @@ -4,10 +4,8 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.util.Base64; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpStatus; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -17,9 +15,6 @@ import org.smartregister.repository.AllSettings; import org.smartregister.repository.BaseRepository; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; import java.util.List; import timber.log.Timber; @@ -37,9 +32,6 @@ */ public class SyncUtils { - - private static final String DETAILS_URL = "/user-details?anm-id="; - private org.smartregister.Context opensrpContent = CoreLibrary.getInstance().context(); private Context context; @@ -49,33 +41,7 @@ public SyncUtils(Context context) { } public boolean verifyAuthorization() { - String baseUrl = opensrpContent.configuration().dristhiBaseURL(); - if (baseUrl.endsWith("/")) { - baseUrl = baseUrl.substring(0, baseUrl.length() - 1); - } - final String username = opensrpContent.allSharedPreferences().fetchRegisteredANM(); - final String password = opensrpContent.allSettings().fetchANMPassword(); - baseUrl = baseUrl + DETAILS_URL + username; - try { - URL url = new URL(baseUrl); - HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - final String basicAuth = "Basic " + Base64.encodeToString((username + ":" + password).getBytes(), Base64.NO_WRAP); - urlConnection.setRequestProperty("Authorization", basicAuth); - int statusCode = urlConnection.getResponseCode(); - urlConnection.disconnect(); - if (statusCode == HttpStatus.SC_UNAUTHORIZED) { - Timber.i("User not authorized. User access was revoked, will log off user"); - return false; - } else if (statusCode != HttpStatus.SC_OK) { - Timber.w("Error occurred verifying authorization, User will not be logged off"); - } else { - Timber.i("User is Authorized"); - } - - } catch (IOException e) { - Timber.e(e); - } - return true; + return CoreLibrary.getInstance().context().getHttpAgent().verifyAuthorization(); } public void logoutUser() { @@ -152,7 +118,9 @@ private boolean isNewerSetting(Setting setting1, Setting setting2) { } private synchronized String getIncrementedServerVersion(Setting setting) { - if (setting == null || StringUtils.isBlank(setting.getVersion())) { return null; } + if (setting == null || StringUtils.isBlank(setting.getVersion())) { + return null; + } return String.valueOf(Long.valueOf(setting.getVersion()) + 1); } diff --git a/opensrp-app/src/main/java/org/smartregister/util/Utils.java b/opensrp-app/src/main/java/org/smartregister/util/Utils.java index c354efe63..21dfce6a6 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/Utils.java +++ b/opensrp-app/src/main/java/org/smartregister/util/Utils.java @@ -24,6 +24,7 @@ import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; +import android.content.res.XmlResourceParser; import android.graphics.Color; import android.net.ConnectivityManager; import android.net.NetworkInfo; @@ -60,7 +61,9 @@ import org.joda.time.Years; import org.smartregister.AllConstants; import org.smartregister.CoreLibrary; +import org.smartregister.R; import org.smartregister.SyncFilter; +import org.smartregister.account.AccountAuthenticatorXml; import org.smartregister.commonregistry.CommonPersonObject; import org.smartregister.commonregistry.CommonPersonObjectClient; import org.smartregister.domain.FetchStatus; @@ -867,13 +870,13 @@ protected static boolean deleteDbJournal(File databases, @NonNull String databas public static String getAppId(@NonNull Context context) { PackageInfo packageInfo = getPackageInfo(context); - return packageInfo != null? packageInfo.packageName : null; + return packageInfo != null ? packageInfo.packageName : null; } @Nullable public static String getAppVersion(@NonNull Context context) { PackageInfo packageInfo = getPackageInfo(context); - return packageInfo != null? packageInfo.versionName : null; + return packageInfo != null ? packageInfo.versionName : null; } @Nullable @@ -906,4 +909,46 @@ public static int calculatePercentage(long totalCount, long partialCount){ } } + protected static String safeArrayToString(char[] array) { + + StringBuilder stringBuilder = new StringBuilder(); + for (char c : array) { + stringBuilder.append(c); + } + return stringBuilder.toString(); + } + + public static AccountAuthenticatorXml parseAuthenticatorXMLConfigData(Context context) { + try { + int eventType = -1; + String namespace = "http://schemas.android.com/apk/res/android"; + AccountAuthenticatorXml authenticatorXml = new AccountAuthenticatorXml(); + XmlResourceParser parser = context.getResources().getXml(R.xml.authenticator); + + while (eventType != XmlResourceParser.END_DOCUMENT) { + if (eventType == XmlResourceParser.START_TAG) { + String element = parser.getName(); + + if ("account-authenticator".equals(element)) { + //Account type id + String accountType = parser.getAttributeValue(namespace, "accountType"); + authenticatorXml.setAccountType(accountType); + + //Account Name + int labelId = parser.getAttributeResourceValue(namespace, "label", 0); + authenticatorXml.setAccountName(context.getResources().getString(labelId)); + + //Icon + int iconImageResourceId = parser.getAttributeResourceValue(namespace, "icon", 0); + authenticatorXml.setIcon(iconImageResourceId); + } + } + eventType = parser.next(); + } + return authenticatorXml; + } catch (Exception e) { + Timber.e(e); + return null; + } + } } \ No newline at end of file From 3d9c7755afe16bdbc90523489e990ea30d275276 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Sat, 9 May 2020 14:56:16 +0300 Subject: [PATCH 06/70] Implement login demo via sample app for oauth --- sample/build.gradle | 8 ++ sample/src/main/AndroidManifest.xml | 12 ++- .../sample/SampleLoginActivity.java | 45 +++++++++++ .../sample/application/SampleApplication.java | 11 +-- .../application/SampleSyncConfiguration.java | 75 +++++++++++++++++++ .../sample/interactor/LoginInteractor.java | 19 +++++ .../sample/presenter/LoginPresenter.java | 29 +++++++ sample/src/main/res/xml/authenticator.xml | 7 ++ sample/src/main/res/xml/preferences.xml | 9 +++ 9 files changed, 205 insertions(+), 10 deletions(-) create mode 100644 sample/src/main/java/org/smartregister/sample/SampleLoginActivity.java create mode 100644 sample/src/main/java/org/smartregister/sample/application/SampleSyncConfiguration.java create mode 100644 sample/src/main/java/org/smartregister/sample/interactor/LoginInteractor.java create mode 100644 sample/src/main/java/org/smartregister/sample/presenter/LoginPresenter.java create mode 100644 sample/src/main/res/xml/authenticator.xml create mode 100644 sample/src/main/res/xml/preferences.xml diff --git a/sample/build.gradle b/sample/build.gradle index 55cf13478..b97f23124 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -52,6 +52,14 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + + debug { + + minifyEnabled false + + buildConfigField "String", "OAUTH_CLIENT_ID", '"opensrp-trusted-client"' + buildConfigField "String", "OAUTH_CLIENT_SECRET", '"O@aTHS#cr3t"' + } } packagingOptions { diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 3100850d6..cdf071f18 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -16,15 +16,21 @@ android:theme="@style/AppTheme" tools:replace="android:theme"> + android:name=".SampleLoginActivity" + android:screenOrientation="fullSensor" + android:theme="@style/AppThemeNoTitle"> + + + diff --git a/sample/src/main/java/org/smartregister/sample/SampleLoginActivity.java b/sample/src/main/java/org/smartregister/sample/SampleLoginActivity.java new file mode 100644 index 000000000..89f4de02c --- /dev/null +++ b/sample/src/main/java/org/smartregister/sample/SampleLoginActivity.java @@ -0,0 +1,45 @@ +package org.smartregister.sample; + +import android.content.Intent; +import android.os.Bundle; +import android.widget.TextView; + +import org.smartregister.sample.presenter.LoginPresenter; +import org.smartregister.view.activity.BaseLoginActivity; +import org.smartregister.view.activity.NativeECSmartRegisterActivity; +import org.smartregister.view.contract.BaseLoginContract; + +/** + * Created by ndegwamartin on 03/05/2020. + */ +public class SampleLoginActivity extends BaseLoginActivity implements BaseLoginContract.View { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + } + + @Override + protected void onResume() { + super.onResume(); + + } + + @Override + protected int getContentView() { + return R.layout.activity_login; + } + + @Override + protected void initializePresenter() { + mLoginPresenter = new LoginPresenter(this); + } + + @Override + public void goToHome(boolean isRemote) { + Intent navigateToRegister = new Intent(this, NativeECSmartRegisterActivity.class); + startActivity(navigateToRegister); + } + +} diff --git a/sample/src/main/java/org/smartregister/sample/application/SampleApplication.java b/sample/src/main/java/org/smartregister/sample/application/SampleApplication.java index a9e969b0c..8e9ba9e6a 100644 --- a/sample/src/main/java/org/smartregister/sample/application/SampleApplication.java +++ b/sample/src/main/java/org/smartregister/sample/application/SampleApplication.java @@ -1,5 +1,7 @@ package org.smartregister.sample.application; +import org.smartregister.AllConstants; +import org.smartregister.BuildConfig; import org.smartregister.Context; import org.smartregister.CoreLibrary; import org.smartregister.view.activity.DrishtiApplication; @@ -23,9 +25,9 @@ public void onCreate() { context.updateApplicationContext(getApplicationContext()); //Initialize Modules - CoreLibrary.init(context, null); + CoreLibrary.init(context, new SampleSyncConfiguration(), BuildConfig.BUILD_TIMESTAMP); - getRepository().getReadableDatabase(); + context.allSharedPreferences().savePreference(AllConstants.DRISHTI_BASE_URL, CoreLibrary.getInstance().context().getAppProperties().getProperty(AllConstants.DRISHTI_BASE_URL)); } public static synchronized SampleApplication getInstance() { @@ -38,9 +40,4 @@ public void logoutCurrentUser() { } - @Override - public String getPassword() { - return "sample-password"; - } - } diff --git a/sample/src/main/java/org/smartregister/sample/application/SampleSyncConfiguration.java b/sample/src/main/java/org/smartregister/sample/application/SampleSyncConfiguration.java new file mode 100644 index 000000000..205ab4b80 --- /dev/null +++ b/sample/src/main/java/org/smartregister/sample/application/SampleSyncConfiguration.java @@ -0,0 +1,75 @@ +package org.smartregister.sample.application; + +import org.smartregister.CoreLibrary; +import org.smartregister.SyncConfiguration; +import org.smartregister.SyncFilter; +import org.smartregister.repository.AllSharedPreferences; +import org.smartregister.sample.BuildConfig; + +import java.util.List; + +/** + * Created by ndegwamartin on 06/05/2020. + */ +public class SampleSyncConfiguration extends SyncConfiguration { + @Override + public int getSyncMaxRetries() { + return 0; + } + + @Override + public SyncFilter getSyncFilterParam() { + return SyncFilter.LOCATION; + } + + @Override + public String getSyncFilterValue() { + AllSharedPreferences sharedPreferences = CoreLibrary.getInstance().context().userService().getAllSharedPreferences(); + return sharedPreferences.fetchDefaultLocalityId(sharedPreferences.fetchRegisteredANM()); + } + + @Override + public int getUniqueIdSource() { + return 0; + } + + @Override + public int getUniqueIdBatchSize() { + return 0; + } + + @Override + public int getUniqueIdInitialBatchSize() { + return 0; + } + + @Override + public SyncFilter getEncryptionParam() { + return SyncFilter.TEAM; + } + + @Override + public boolean updateClientDetailsTable() { + return false; + } + + @Override + public List getSynchronizedLocationTags() { + return null; + } + + @Override + public String getTopAllowedLocationLevel() { + return null; + } + + @Override + public String getOauthClientId() { + return BuildConfig.OAUTH_CLIENT_ID; + } + + @Override + public String getOauthClientSecret() { + return BuildConfig.OAUTH_CLIENT_SECRET; + } +} diff --git a/sample/src/main/java/org/smartregister/sample/interactor/LoginInteractor.java b/sample/src/main/java/org/smartregister/sample/interactor/LoginInteractor.java new file mode 100644 index 000000000..3cea406d0 --- /dev/null +++ b/sample/src/main/java/org/smartregister/sample/interactor/LoginInteractor.java @@ -0,0 +1,19 @@ +package org.smartregister.sample.interactor; + +import org.smartregister.login.interactor.BaseLoginInteractor; +import org.smartregister.view.contract.BaseLoginContract; + +/** + * Created by ndegwamartin on 08/05/2020. + */ +public class LoginInteractor extends BaseLoginInteractor { + + public LoginInteractor(BaseLoginContract.Presenter loginPresenter) { + super(loginPresenter); + } + + @Override + protected void scheduleJobsPeriodically() { + //Schedule your jobs here + } +} diff --git a/sample/src/main/java/org/smartregister/sample/presenter/LoginPresenter.java b/sample/src/main/java/org/smartregister/sample/presenter/LoginPresenter.java new file mode 100644 index 000000000..1708c2383 --- /dev/null +++ b/sample/src/main/java/org/smartregister/sample/presenter/LoginPresenter.java @@ -0,0 +1,29 @@ +package org.smartregister.sample.presenter; + +import org.smartregister.login.model.BaseLoginModel; +import org.smartregister.login.presenter.BaseLoginPresenter; +import org.smartregister.sample.interactor.LoginInteractor; +import org.smartregister.view.contract.BaseLoginContract; + +import java.lang.ref.WeakReference; + +/** + * Created by ndegwamartin on 08/05/2020. + */ +public class LoginPresenter extends BaseLoginPresenter { + + public LoginPresenter(BaseLoginContract.View loginView) { + mLoginView = new WeakReference<>(loginView); + mLoginInteractor = new LoginInteractor(this); + mLoginModel = new BaseLoginModel(); + } + @Override + public void processViewCustomizations() { + //Do nothing + } + + @Override + public boolean isServerSettingsSet() { + return false; + } +} diff --git a/sample/src/main/res/xml/authenticator.xml b/sample/src/main/res/xml/authenticator.xml new file mode 100644 index 000000000..f0e776cd2 --- /dev/null +++ b/sample/src/main/res/xml/authenticator.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/sample/src/main/res/xml/preferences.xml b/sample/src/main/res/xml/preferences.xml new file mode 100644 index 000000000..9324fb624 --- /dev/null +++ b/sample/src/main/res/xml/preferences.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file From e8382b2ab111640dddb25b969fc2e50ce9e08e2c Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Mon, 11 May 2020 19:00:10 +0300 Subject: [PATCH 07/70] Refactor HTTPAgent Fix failing unit tests --- .../smartregister/repository/AllSettings.java | 9 +- .../org/smartregister/service/HTTPAgent.java | 477 +++++++++++++++--- .../smartregister/TestSyncConfiguration.java | 10 + .../repository/AllSettingsTest.java | 32 +- .../smartregister/service/HTTPAgentTest.java | 74 ++- .../service/UserServiceTest.java | 4 +- .../fragment/BaseRegisterFragmentTest.java | 2 + 7 files changed, 503 insertions(+), 105 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/repository/AllSettings.java b/opensrp-app/src/main/java/org/smartregister/repository/AllSettings.java index ebb697991..b063c8819 100644 --- a/opensrp-app/src/main/java/org/smartregister/repository/AllSettings.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/AllSettings.java @@ -10,7 +10,6 @@ public class AllSettings { public static final String APPLIED_VILLAGE_FILTER_SETTING_KEY = "appliedVillageFilter"; public static final String PREVIOUS_FETCH_INDEX_SETTING_KEY = "previousFetchIndex"; public static final String PREVIOUS_FORM_SYNC_INDEX_SETTING_KEY = "previousFormSyncIndex"; - private static final String ANM_PASSWORD_PREFERENCE_KEY = "anmPassword"; private static final String ANM_LOCATION = "anmLocation"; private static final String ANM_TEAM = "anmTeam"; private static final String USER_INFORMATION = "userInformation"; @@ -23,9 +22,8 @@ public AllSettings(AllSharedPreferences preferences, SettingsRepository settings this.settingsRepository = settingsRepository; } - public void registerANM(String userName, String password) { + public void registerANM(String userName) { preferences.updateANMUserName(userName); - settingsRepository.updateSetting(ANM_PASSWORD_PREFERENCE_KEY, password); } public void savePreviousFetchIndex(String value) { @@ -45,10 +43,6 @@ public String appliedVillageFilter(String defaultFilterValue) { .querySetting(APPLIED_VILLAGE_FILTER_SETTING_KEY, defaultFilterValue); } - public String fetchANMPassword() { - return settingsRepository.querySetting(ANM_PASSWORD_PREFERENCE_KEY, ""); - } - public String fetchPreviousFormSyncIndex() { return settingsRepository.querySetting(PREVIOUS_FORM_SYNC_INDEX_SETTING_KEY, "0"); } @@ -80,7 +74,6 @@ public String fetchUserInformation() { public Map getAuthParams() { Map authParams = new HashMap(); authParams.put("username", preferences.fetchRegisteredANM()); - authParams.put("password", fetchANMPassword()); return authParams; } diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index c2f446ac3..251e3f1c5 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -1,13 +1,26 @@ package org.smartregister.service; +import android.accounts.Account; +import android.accounts.AccountManager; import android.content.Context; +import android.support.annotation.NonNull; import android.util.Base64; +import android.util.Log; + +import com.google.common.io.BaseEncoding; +import com.google.gson.Gson; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; +import org.apache.http.util.ByteArrayBuffer; import org.smartregister.AllConstants; +import org.smartregister.CoreLibrary; import org.smartregister.DristhiConfiguration; +import org.smartregister.account.AccountAuthenticatorXml; +import org.smartregister.account.AccountError; +import org.smartregister.account.AccountHelper; +import org.smartregister.account.AccountResponse; import org.smartregister.compression.GZIPCompression; import org.smartregister.domain.DownloadStatus; import org.smartregister.domain.LoginResponse; @@ -19,13 +32,15 @@ import org.smartregister.repository.AllSettings; import org.smartregister.repository.AllSharedPreferences; import org.smartregister.ssl.OpensrpSSLHelper; -import org.smartregister.util.DownloadForm; +import org.smartregister.util.SyncUtils; import org.smartregister.util.Utils; +import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -79,24 +94,21 @@ public class HTTPAgent { private int connectTimeout = 60000; private int readTimeout = 60000; + private static final String DETAILS_URL = "/user-details?anm-id="; + + private SyncUtils syncUtils; + public HTTPAgent(Context context, AllSettings settings, AllSharedPreferences allSharedPreferences, DristhiConfiguration configuration) { this.context = context; this.settings = settings; this.allSharedPreferences = allSharedPreferences; this.configuration = configuration; - gzipCompression= new GZIPCompression(); + gzipCompression = new GZIPCompression(); + syncUtils = new SyncUtils(context.getApplicationContext()); } - /** - * @author Rodgers Andati - * @since 2019-04-25 - * This method initializes httpurlconnection - * @param requestURLPath This is the url to be open http connection to. - * @param useBasicAuth This is whether to set up basic authentication or not. - * @return HttpURLConnection This returns the http connection to opensrp server. - */ - private HttpURLConnection initializeHttp(String requestURLPath, boolean useBasicAuth) throws IOException { + private HttpURLConnection initializeHttp(String requestURLPath) throws IOException { URL url = new URL(requestURLPath); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); if (urlConnection instanceof HttpsURLConnection) { @@ -105,19 +117,18 @@ private HttpURLConnection initializeHttp(String requestURLPath, boolean useBasic } urlConnection.setConnectTimeout(getConnectTimeout()); urlConnection.setReadTimeout(getReadTimeout()); + AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); + if (AccountHelper.getOauthAccountByType(authenticatorXml.getAccountType()) != null) + urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); - if(useBasicAuth) { - final String basicAuth = "Basic " + Base64.encodeToString((allSharedPreferences.fetchRegisteredANM() + - ":" + settings.fetchANMPassword()).getBytes(), Base64.NO_WRAP); - urlConnection.setRequestProperty("Authorization", basicAuth); - } return urlConnection; } public Response fetch(String requestURLPath) { - HttpURLConnection urlConnection; try { - urlConnection = initializeHttp(requestURLPath, true); + + HttpURLConnection urlConnection = httpURLConnectionTries(requestURLPath); + return handleResponse(urlConnection); } catch (IOException ex) { @@ -126,15 +137,39 @@ public Response fetch(String requestURLPath) { } } + @NonNull + private HttpURLConnection httpURLConnectionTries(String requestURLPath) throws IOException { + + int authRetries = 0; + HttpURLConnection urlConnection = null; + + while (authRetries < CoreLibrary.getInstance().getSyncConfiguration().getMaxAuthenticationRetries() + 1) { + + authRetries++; + + urlConnection = initializeHttp(requestURLPath); + + if (urlConnection.getResponseCode() == HttpStatus.SC_UNAUTHORIZED) { + + refreshAuthenticationToken(AccountHelper.getAccountManagerValue(AccountHelper.KEY_REFRESH_TOKEN, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); + } + + } + return urlConnection; + } + public Response post(String postURLPath, String jsonPayload) { HttpURLConnection urlConnection; + AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); try { - urlConnection = initializeHttp(postURLPath, true); + urlConnection = initializeHttp(postURLPath); urlConnection.setDoOutput(true); urlConnection.setRequestMethod("POST"); urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8"); urlConnection.setRequestProperty("Content-Encoding", "gzip"); + urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); + OutputStream os = urlConnection.getOutputStream(); BufferedOutputStream writer = new BufferedOutputStream(os); @@ -148,7 +183,7 @@ public Response post(String postURLPath, String jsonPayload) { return handleResponse(urlConnection); } catch (IOException ex) { - Timber.e(ex, "EXCEPTION: %s", ex.toString()); + Timber.e(ex, "EXCEPTION: %s", ex.toString()); return new Response<>(ResponseStatus.failure, null); } } @@ -159,7 +194,7 @@ public Response postWithJsonResponse(String postURLPath, String jsonPayl } private void logResponse(String postURLPath, String jsonPayload) { - Timber.d("postURLPath: %s and jsonPayLoad: %s", postURLPath,jsonPayload); + Timber.d("postURLPath: %s and jsonPayLoad: %s", postURLPath, jsonPayload); } public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, String userName, String password) { @@ -168,7 +203,7 @@ public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, Strin String url = null; try { url = requestURL.replaceAll("\\s+", ""); - urlConnection = initializeHttp(url, false); + urlConnection = initializeHttp(url); final String basicAuth = "Basic " + Base64.encodeToString((userName + ":" + password).getBytes(), Base64.NO_WRAP); urlConnection.setRequestProperty("Authorization", basicAuth); @@ -200,14 +235,14 @@ public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, Strin loginResponse = UNKNOWN_RESPONSE; } } catch (MalformedURLException e) { - Timber.e(e, "Failed to check credentials bad url %s", url); + Timber.e(e, "Failed to check credentials bad url %s", url); loginResponse = MALFORMED_URL; } catch (SocketTimeoutException e) { - Timber.e(e,"SocketTimeoutException when authenticating %s", userName); + Timber.e(e, "SocketTimeoutException when authenticating %s", userName); loginResponse = TIMEOUT; - Timber.e(e,"Failed to check credentials of: %s using %s . Error: %s", userName, url, e.toString()); + Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", userName, url, e.toString()); } catch (IOException e) { - Timber.e(e,"Failed to check credentials of: %s using %s . Error: %s", userName, url, e.toString()); + Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", userName, url, e.toString()); loginResponse = NO_INTERNET_CONNECTIVITY; } finally { if (urlConnection != null) { @@ -218,41 +253,40 @@ public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, Strin } public DownloadStatus downloadFromUrl(String url, String filename) { - Response status = DownloadForm.downloadFromURL(url, filename, - allSharedPreferences.fetchRegisteredANM(), settings.fetchANMPassword()); - Timber.d("downloading file name : %s and url %s", filename,url); + + AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); + Response status = downloadFromURL(url, filename, new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); + Timber.d("downloading file name : %s and url %s", filename, url); return status.payload(); } - public Response fetchWithCredentials(String requestURL, String username, String password) { - HttpURLConnection urlConnection = null; + public Response fetchWithCredentials(String requestURL) { + try { - urlConnection = initializeHttp(requestURL, false); - setCustomCredentials(urlConnection, username, password); + HttpURLConnection urlConnection = httpURLConnectionTries(requestURL); return handleResponse(urlConnection); } catch (IOException ex) { - Timber.e(ex, "EXCEPTION %s", ex.toString()); + Timber.e(ex, "EXCEPTION %s", ex.toString()); return new Response<>(ResponseStatus.failure, null); } } - private void setCustomCredentials(HttpURLConnection urlConnection, String username, String password) { - final String basicAuth = "Basic " + Base64.encodeToString((username + ":" + password).getBytes(), - Base64.NO_WRAP); - urlConnection.setRequestProperty("Authorization", basicAuth); - } - private Response handleResponse(HttpURLConnection urlConnection) { String responseString; String totalRecords; try { int statusCode = urlConnection.getResponseCode(); - InputStream inputStream; - if (statusCode >= HttpStatus.SC_BAD_REQUEST) + InputStream inputStream = null; + + if (statusCode == HttpStatus.SC_UNAUTHORIZED) { + + syncUtils.logoutUser(); + + } else if (statusCode >= HttpStatus.SC_BAD_REQUEST) inputStream = urlConnection.getErrorStream(); else inputStream = urlConnection.getInputStream(); @@ -264,48 +298,51 @@ private Response handleResponse(HttpURLConnection urlConnection) { Timber.d("response string: %s using url %s", responseString, urlConnection.getURL()); } catch (MalformedURLException e) { - Timber.e(e, "%s %s", MALFORMED_URL, e.toString()); + Timber.e(e, "%s %s", MALFORMED_URL, e.toString()); ResponseStatus.failure.setDisplayValue(ResponseErrorStatus.malformed_url.name()); return new Response<>(ResponseStatus.failure, null); } catch (SocketTimeoutException e) { - Timber.e(e, "%s %s", TIMEOUT, e.toString()); + Timber.e(e, "%s %s", TIMEOUT, e.toString()); ResponseStatus.failure.setDisplayValue(ResponseErrorStatus.timeout.name()); return new Response<>(ResponseStatus.failure, null); } catch (IOException e) { - Timber.e(e, "%s %s", NO_INTERNET_CONNECTIVITY, e.toString()); + Timber.e(e, "%s %s", NO_INTERNET_CONNECTIVITY, e.toString()); return new Response<>(ResponseStatus.failure, null); } finally { if (urlConnection != null) { urlConnection.disconnect(); } } - return new Response<>(ResponseStatus.success, responseString).withTotalRecords(Utils.tryParseLong(totalRecords,0)); + return new Response<>(ResponseStatus.success, responseString).withTotalRecords(Utils.tryParseLong(totalRecords, 0)); } /** - * @author Rodgers Andati - * @since 2019-04-25 - * This method uploads an image to opensrp server. Migration from the old method that used httpclient * @param urlString This is the url of the image, TAG, - * @param image This is the image to be uploaded to opensrp server. + * @param image This is the image to be uploaded to opensrp server. * @return String This returns the response obtained from the opensrp server. + * @author Rodgers Andati + * @since 2019-04-25 + * This method uploads an image to opensrp server. Migration from the old method that used httpclient */ public String httpImagePost(String urlString, ProfileImage image) { OutputStream outputStream; PrintWriter writer; String responseString = ""; + AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); try { - HttpURLConnection httpUrlConnection = initializeHttp(urlString, true); + HttpURLConnection httpUrlConnection = initializeHttp(urlString); httpUrlConnection.setUseCaches(false); httpUrlConnection.setDoInput(true); httpUrlConnection.setDoOutput(true); httpUrlConnection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); + httpUrlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); + outputStream = httpUrlConnection.getOutputStream(); - writer = new PrintWriter(new OutputStreamWriter(outputStream, "UTF-8"),true); + writer = new PrintWriter(new OutputStreamWriter(outputStream, "UTF-8"), true); // attach image attachImage(writer, image, outputStream); @@ -346,7 +383,7 @@ public String httpImagePost(String urlString, ProfileImage image) { } catch (ProtocolException e) { Timber.e(e, "Protocol exception %s", e.toString()); } catch (SocketTimeoutException e) { - Timber.e(e, "SocketTimeout %s %s", TIMEOUT, e.toString()); + Timber.e(e, "SocketTimeout %s %s", TIMEOUT, e.toString()); } catch (MalformedURLException e) { Timber.e(e, "MalformedUrl %s %s", MALFORMED_URL, e.toString()); } catch (IOException e) { @@ -381,13 +418,13 @@ private void attachImage(PrintWriter writer, ProfileImage image, OutputStream ou private void addParameter(PrintWriter writer, String paramName, String paramValue) { writer.append(twoHyphens + boundary).append(crlf); - writer.append("Content-Disposition: form-data; name=\""+ paramName +"\"").append(crlf); + writer.append("Content-Disposition: form-data; name=\"" + paramName + "\"").append(crlf); writer.append("Content-Type: text/plain; charset=" + "UTF-8").append(crlf); writer.append(crlf); writer.append(paramValue).append(crlf); writer.flush(); - Timber.d("http agent param name: %s and param value %s ", paramName,paramValue); + Timber.d("http agent param name: %s and param value %s ", paramName, paramValue); } @@ -463,7 +500,7 @@ public int getConnectTimeout() { /** * Sets the connection timeout in milliseconds - * + *

* Setting this will call {@link java.net.HttpURLConnection#setConnectTimeout(int)} * on the {@link java.net.HttpURLConnection} instance in {@link org.smartregister.service.HTTPAgent} */ @@ -473,11 +510,335 @@ public void setConnectTimeout(int connectTimeout) { /** * Sets the read timeout in milliseconds - * + *

* Setting this will call {@link java.net.HttpURLConnection#setReadTimeout(int)} * on the {@link java.net.HttpURLConnection} instance in {@link org.smartregister.service.HTTPAgent} */ public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } -} + + public AccountResponse oauth2authenticate(String username, String password, String grantType) { + + AccountError accountError = null; + HttpURLConnection urlConnection = null; + String url = null; + String baseURL = configuration.dristhiBaseURL() + AccountHelper.OAUTH.TOKEN_ENDPOINT; + String requestURL = baseURL + "?&grant_type=" + grantType + "&username=" + username + "&password=" + password; + try { + url = requestURL.replaceAll("\\s+", ""); + urlConnection = initializeHttp(url); + + String clientId = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientId(); + String clientSecret = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientSecret(); + + final String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes()); + + urlConnection.setRequestMethod("POST"); + urlConnection.addRequestProperty("client_id", clientId); + urlConnection.addRequestProperty("client_secret", clientSecret); + urlConnection.setRequestProperty("Authorization", "Basic " + base64Auth); + + + int statusCode = urlConnection.getResponseCode(); + InputStream inputStream; + if (statusCode >= HttpStatus.SC_BAD_REQUEST) + inputStream = urlConnection.getErrorStream(); + else + inputStream = urlConnection.getInputStream(); + String responseString = IOUtils.toString(inputStream); + if (statusCode == HttpStatus.SC_OK) { + + Timber.d("response String: %s using request url %s", responseString, url); + + AccountResponse accountResponse = new Gson().fromJson(responseString, AccountResponse.class); + return accountResponse; + + } else { + + accountError = new Gson().fromJson(responseString, AccountError.class); + return new AccountResponse(statusCode, accountError); + + } + } catch (MalformedURLException e) { + Timber.e(e, "Failed to check credentials bad url %s", url); + accountError = new AccountError(0, MALFORMED_URL.name()); + + } catch (SocketTimeoutException e) { + Timber.e(e, "SocketTimeoutException when authenticating %s", username); + + accountError = new AccountError(0, TIMEOUT.name()); + + Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", username, url, e.toString()); + } catch (IOException e) { + Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", username, url, e.toString()); + accountError = new AccountError(0, NO_INTERNET_CONNECTIVITY.name()); + + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + + } + + //If we got here there was an issue with no server status code + return new AccountResponse(0, accountError); + + } + + public LoginResponse fetchUserDetails(String requestURL, String oauthAccessToken) { + LoginResponse loginResponse = null; + String url = null; + HttpURLConnection urlConnection = null; + try { + url = requestURL.replaceAll("\\s+", ""); + + urlConnection = httpURLConnectionTries(url); + + int statusCode = urlConnection.getResponseCode(); + + InputStream inputStream; + if (statusCode >= HttpStatus.SC_BAD_REQUEST) + inputStream = urlConnection.getErrorStream(); + else + inputStream = urlConnection.getInputStream(); + String responseString = IOUtils.toString(inputStream); + if (statusCode == HttpStatus.SC_OK) { + + Timber.d("response String: %s using request url %s", responseString, url); + LoginResponseData responseData = getResponseBody(responseString); + loginResponse = retrieveResponse(responseData); + } else if (statusCode == HttpStatus.SC_UNAUTHORIZED) { + Timber.e("Invalid credentials for: %s using token %s", oauthAccessToken, url); + loginResponse = UNAUTHORIZED; + } else if (StringUtils.isNotBlank(responseString)) { + //extract message string from the default tomcat server response which is usually between

message and

+ responseString = StringUtils.substringBetween(responseString, "

message", "

"); + if (StringUtils.isNotBlank(responseString)) { + //remove the underline tag from the responseString + responseString = responseString.replace("", "").trim(); + loginResponse = CUSTOM_SERVER_RESPONSE.withMessage(responseString); + } + } else { + Timber.e("Bad response from Server. Status code: %s using %s ", statusCode, url); + loginResponse = UNKNOWN_RESPONSE; + } + } catch (MalformedURLException e) { + Timber.e(e, "Failed to check credentials bad url %s", url); + loginResponse = MALFORMED_URL; + } catch (SocketTimeoutException e) { + Timber.e(e, "SocketTimeoutException when authenticating"); + loginResponse = TIMEOUT; + } catch (IOException e) { + Timber.e(e, "Failed to connect to %s check, check internet connection. Error: %s", url, e.toString()); + loginResponse = NO_INTERNET_CONNECTIVITY; + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + } + return loginResponse; + } + + /** + * @param downloadURL_ This is the url of the image + * @param fileName This is how the image should be name after it has been downloaded. + * @param accessToken This is the access token used to authenticate when accessing the url endpoint. + * @return Response This returns whether the download succeeded or failed. + */ + public Response downloadFromURL(String downloadURL_, String fileName, String accessToken) { + HttpURLConnection httpUrlConnection; + try { + File dir = new File(FormPathService.sdcardPathDownload); + + if (!dir.exists()) { + dir.mkdirs(); + } + + File file = new File(dir, fileName); + + long startTime = System.currentTimeMillis(); + Log.d("DownloadFormService", "download begin"); + Log.d("DownloadFormService", "download url: " + downloadURL_); + Log.d("DownloadFormService", "download file name: " + fileName); + + + String downloadURL = downloadURL_.replaceAll("\\s+", ""); + + /* Open connection to URL */ + URL url = new URL(downloadURL); + + httpUrlConnection = (HttpURLConnection) url.openConnection(); + + if (httpUrlConnection instanceof HttpsURLConnection) { + OpensrpSSLHelper opensrpSSLHelper = new OpensrpSSLHelper(context, configuration); + ((HttpsURLConnection) httpUrlConnection).setSSLSocketFactory(opensrpSSLHelper.getSSLSocketFactory()); + } + + httpUrlConnection.setRequestProperty("Authorization", accessToken); + + httpUrlConnection.connect(); + + int status = httpUrlConnection.getResponseCode(); + if (status == HttpURLConnection.HTTP_OK) { + + InputStream inputStream = httpUrlConnection.getInputStream(); + BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); + + long fileLength = bufferedInputStream.available(); + if (fileLength == 0) { + return new Response(ResponseStatus.success, + DownloadStatus.nothingDownloaded); + } + Log.d("DownloadFormService", "file length : " + fileLength); + + ByteArrayBuffer baf = new ByteArrayBuffer(9999); + int current = 0; + while ((current = bufferedInputStream.read()) != -1) { + baf.append((byte) current); + } + + /* Convert the bytes to String */ + FileOutputStream fos = new FileOutputStream(file); + fos.write(baf.toByteArray()); + fos.flush(); + fos.close(); + + Log.d("DownloadFormService", + "download finished in " + ((System.currentTimeMillis() - startTime) / 1000) + + " sec"); + httpUrlConnection.disconnect(); + + } else { + Log.d("RESPONSE", "Server returned non-OK status: " + status); + return new Response(ResponseStatus.failure, DownloadStatus.failedDownloaded); + } + + } catch (IOException e) { + Log.d("DownloadFormService", "download error : " + e); + return new Response(ResponseStatus.success, DownloadStatus.failedDownloaded); + } + + return new Response(ResponseStatus.success, DownloadStatus.downloaded); + } + + public boolean verifyAuthorization() { + String baseUrl = configuration.dristhiBaseURL(); + + if (baseUrl.endsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + } + final String username = allSharedPreferences.fetchRegisteredANM(); + baseUrl = baseUrl + DETAILS_URL + username; + try { + URL url = new URL(baseUrl); + HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + + AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); + if (AccountHelper.getOauthAccountByType(authenticatorXml.getAccountType()) != null) + urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); + int statusCode = urlConnection.getResponseCode(); + urlConnection.disconnect(); + if (statusCode == HttpStatus.SC_UNAUTHORIZED) { + Timber.i("User not authorized. User access was revoked, will log off user"); + return false; + } else if (statusCode != HttpStatus.SC_OK) { + Timber.w("Error occurred verifying authorization, User will not be logged off"); + } else { + Timber.i("User is Authorized"); + } + + } catch (IOException e) { + Timber.e(e); + } + return true; + } + + public AccountResponse oauth2authenticateRefreshToken(String refreshToken) { + + AccountError accountError = null; + HttpURLConnection urlConnection = null; + String url = null; + + String clientId = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientId(); + String clientSecret = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientSecret(); + + String baseURL = configuration.dristhiBaseURL() + AccountHelper.OAUTH.TOKEN_ENDPOINT; + String requestURL = baseURL + "?&grant_type=" + AccountHelper.OAUTH.GRANT_TYPE.REFRESH_TOKEN + "&refresh_token=" + refreshToken + "&client_id=" + clientId; + try { + url = requestURL.replaceAll("\\s+", ""); + urlConnection = initializeHttp(url); + + + final String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes()); + + urlConnection.setRequestMethod("POST"); + urlConnection.addRequestProperty("client_id", clientId); + urlConnection.addRequestProperty("client_secret", clientSecret); + urlConnection.setRequestProperty("Authorization", "Bearer " + base64Auth); + + + int statusCode = urlConnection.getResponseCode(); + InputStream inputStream; + if (statusCode >= HttpStatus.SC_BAD_REQUEST) + inputStream = urlConnection.getErrorStream(); + else + inputStream = urlConnection.getInputStream(); + String responseString = IOUtils.toString(inputStream); + if (statusCode == HttpStatus.SC_OK) { + + Timber.d("response String: %s using request url %s", responseString, url); + + AccountResponse accountResponse = new Gson().fromJson(responseString, AccountResponse.class); + return accountResponse; + + } else { + + accountError = new Gson().fromJson(responseString, AccountError.class); + return new AccountResponse(statusCode, accountError); + + } + } catch (MalformedURLException e) { + Timber.e(e, "Failed to check credentials bad url %s", url); + accountError = new AccountError(0, MALFORMED_URL.name()); + + } catch (SocketTimeoutException e) { + Timber.e(e, "SocketTimeoutException when authenticating %s", refreshToken); + + accountError = new AccountError(0, TIMEOUT.name()); + + Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", refreshToken, url, e.toString()); + } catch (IOException e) { + Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", refreshToken, url, e.toString()); + accountError = new AccountError(0, NO_INTERNET_CONNECTIVITY.name()); + + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + + } + + //If we got here there was an issue with no server status code + return new AccountResponse(0, accountError); + + } + + private String refreshAuthenticationToken(String refreshToken) { + + AccountResponse response = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticateRefreshToken(refreshToken); + + AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); + AccountManager accountManager = CoreLibrary.getInstance().getAccountManager(); + + + Account account = AccountHelper.getOauthAccountByType(authenticatorXml.getAccountType()); + String authToken = accountManager.peekAuthToken(account, AccountHelper.TOKEN_TYPE.PROVIDER); + + accountManager.setAuthToken(account, AccountHelper.TOKEN_TYPE.PROVIDER, response.getAccessToken()); + accountManager.setPassword(account, response.getAccessToken()); + accountManager.setUserData(account, AccountHelper.KEY_REFRESH_TOKEN, response.getRefreshToken()); + + return response.getAccessToken(); + } +} \ No newline at end of file diff --git a/opensrp-app/src/test/java/org/smartregister/TestSyncConfiguration.java b/opensrp-app/src/test/java/org/smartregister/TestSyncConfiguration.java index 1ff906ecd..675cbc9ff 100644 --- a/opensrp-app/src/test/java/org/smartregister/TestSyncConfiguration.java +++ b/opensrp-app/src/test/java/org/smartregister/TestSyncConfiguration.java @@ -55,4 +55,14 @@ public List getSynchronizedLocationTags() { public String getTopAllowedLocationLevel() { return null; } + + @Override + public String getOauthClientId() { + return "opensrp-client-id"; + } + + @Override + public String getOauthClientSecret() { + return "$om3cl13nt$3cret"; + } } diff --git a/opensrp-app/src/test/java/org/smartregister/repository/AllSettingsTest.java b/opensrp-app/src/test/java/org/smartregister/repository/AllSettingsTest.java index 37e327cde..c3bca5f8a 100644 --- a/opensrp-app/src/test/java/org/smartregister/repository/AllSettingsTest.java +++ b/opensrp-app/src/test/java/org/smartregister/repository/AllSettingsTest.java @@ -1,7 +1,6 @@ package org.smartregister.repository; import org.junit.Assert; - import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -35,16 +34,6 @@ public void setUp() throws Exception { allSettings = new AllSettings(allSharedPreferences, settingsRepository); } - @Test - public void shouldFetchANMPassword() throws Exception { - Mockito.when(settingsRepository.querySetting("anmPassword", "")).thenReturn("actual password"); - - String actual = allSettings.fetchANMPassword(); - - Mockito.verify(settingsRepository).querySetting("anmPassword", ""); - Assert.assertEquals("actual password", actual); - } - @Test public void shouldSavePreviousFetchIndex() throws Exception { allSettings.savePreviousFetchIndex("1234"); @@ -96,10 +85,8 @@ public void shouldGetAppliedVillageFilter() throws Exception { @Test public void assertRegisterANMCallsPreferenceAndRepositoryUpdate() throws Exception { Mockito.doNothing().when(allSharedPreferences).updateANMUserName(Mockito.anyString()); - Mockito.doNothing().doNothing().when(settingsRepository).updateSetting(Mockito.anyString(), Mockito.anyString()); - allSettings.registerANM("", ""); + allSettings.registerANM(""); Mockito.verify(allSharedPreferences, Mockito.times(1)).updateANMUserName(Mockito.anyString()); - Mockito.verify(settingsRepository, Mockito.times(1)).updateSetting(Mockito.anyString(), Mockito.anyString()); } @Test @@ -133,10 +120,8 @@ public void assertSaveUserInformationCallsRepositoryUpdate() { @Test public void assertGetAuthParamsReturnsUserNamePassword() { Mockito.when(allSharedPreferences.fetchRegisteredANM()).thenReturn("username"); - Mockito.when(settingsRepository.querySetting(Mockito.anyString(), Mockito.anyString())).thenReturn("password"); Map auth = allSettings.getAuthParams(); Assert.assertEquals("username", auth.get("username")); - Assert.assertEquals("password", auth.get("password")); } @Test @@ -177,7 +162,8 @@ public void testGet() { @Test public void testGetSetting() { Setting s = new Setting(); - s.setKey("testKey"); s.setValue("testValue"); + s.setKey("testKey"); + s.setValue("testValue"); Mockito.when(settingsRepository.querySetting("testKey")).thenReturn(s); @@ -193,10 +179,12 @@ public void testGetSettingsByType() { List ls = new ArrayList<>(); Setting s = new Setting(); - s.setKey("testKey"); s.setValue("testValue"); + s.setKey("testKey"); + s.setValue("testValue"); ls.add(s); Setting s2 = new Setting(); - s2.setKey("testKey2"); s2.setValue("testValue2"); + s2.setKey("testKey2"); + s2.setValue("testValue2"); ls.add(s2); Mockito.when(settingsRepository.querySettingsByType("testType")).thenReturn(ls); @@ -211,7 +199,8 @@ public void testGetSettingsByType() { @Test public void testPutSetting() { Setting s = new Setting(); - s.setKey("testKey"); s.setValue("testValue"); + s.setKey("testKey"); + s.setValue("testValue"); allSettings.putSetting(s); @@ -223,7 +212,8 @@ public void testGetUnsyncedSettings() { List ls = new ArrayList<>(); Setting s = new Setting(); - s.setKey("testUnsyncedKey"); s.setValue("testUnsyncedValue"); + s.setKey("testUnsyncedKey"); + s.setValue("testUnsyncedValue"); ls.add(s); Mockito.when(settingsRepository.queryUnsyncedSettings()).thenReturn(ls); diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index 7018abce6..8a9022051 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -1,6 +1,7 @@ package org.smartregister.service; -import android.content.Context; +import android.accounts.Account; +import android.accounts.AccountManager; import android.util.Base64; import org.json.JSONObject; @@ -12,11 +13,17 @@ import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import org.smartregister.Context; +import org.smartregister.CoreLibrary; import org.smartregister.DristhiConfiguration; +import org.smartregister.SyncConfiguration; +import org.smartregister.account.AccountAuthenticatorXml; +import org.smartregister.account.AccountHelper; import org.smartregister.domain.LoginResponse; import org.smartregister.domain.ProfileImage; import org.smartregister.domain.Response; @@ -29,10 +36,13 @@ import java.util.HashMap; @RunWith(PowerMockRunner.class) -@PrepareForTest({Base64.class, File.class, FileInputStream.class}) +@PrepareForTest({Base64.class, File.class, FileInputStream.class, Context.class, AccountHelper.class, CoreLibrary.class}) public class HTTPAgentTest { @Mock - private Context context; + private android.content.Context context; + + @Mock + private Context openSrpContext; @Mock private AllSettings allSettings; @@ -46,6 +56,21 @@ public class HTTPAgentTest { @Mock private ProfileImage profileImage; + @Mock + private AccountAuthenticatorXml accountAuthenticatorXml; + + @Mock + private Account account; + + @Mock + private CoreLibrary coreLibrary; + + @Mock + private AccountManager accountManager; + + @Mock + private SyncConfiguration syncConfiguration; + @Rule private TemporaryFolder folder = new TemporaryFolder(); @@ -54,24 +79,41 @@ public class HTTPAgentTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); + + PowerMockito.mockStatic(Context.class); + PowerMockito.when(Context.getInstance()).thenReturn(openSrpContext); + Mockito.doReturn(context).when(context).getApplicationContext(); + + PowerMockito.mockStatic(CoreLibrary.class); + PowerMockito.when(CoreLibrary.getInstance()).thenReturn(coreLibrary); + Mockito.doReturn(accountManager).when(coreLibrary).getAccountManager(); + Mockito.doReturn(accountAuthenticatorXml).when(coreLibrary).getAccountAuthenticatorXml(); + + Mockito.doReturn(accountManager).when(coreLibrary).getAccountManager(); + Mockito.doReturn(syncConfiguration).when(coreLibrary).getSyncConfiguration(); + Mockito.doReturn(1).when(syncConfiguration).getMaxAuthenticationRetries(); + + PowerMockito.mockStatic(AccountHelper.class); + PowerMockito.when(AccountHelper.getOauthAccountByType(accountAuthenticatorXml.getAccountType())).thenReturn(account); + httpAgent = new HTTPAgent(context, allSettings, allSharedPreferences, dristhiConfiguration); } @Test - public void testFetchFailsGivenWrongUrl(){ + public void testFetchFailsGivenWrongUrl() { Response resp = httpAgent.fetch("wrong.url"); Assert.assertEquals(ResponseStatus.failure, resp.status()); } @Test - public void testFetchPassesGivenCorrectUrl(){ + public void testFetchPassesGivenCorrectUrl() { PowerMockito.mockStatic(Base64.class); Response resp = httpAgent.fetch("https://google.com"); Assert.assertEquals(ResponseStatus.success, resp.status()); } @Test - public void testPostFailsGivenWrongUrl(){ + public void testPostFailsGivenWrongUrl() { HashMap map = new HashMap<>(); map.put("title", "OpenSRP Testing Tuesdays"); JSONObject jObject = new JSONObject(map); @@ -80,7 +122,7 @@ public void testPostFailsGivenWrongUrl(){ } @Test - public void testPostPassesGivenCorrectUrl(){ + public void testPostPassesGivenCorrectUrl() { PowerMockito.mockStatic(Base64.class); HashMap map = new HashMap<>(); map.put("title", "OpenSRP Testing Tuesdays"); @@ -90,14 +132,14 @@ public void testPostPassesGivenCorrectUrl(){ } @Test - public void testUrlCanBeAccessWithGivenCredentials(){ + public void testUrlCanBeAccessWithGivenCredentials() { PowerMockito.mockStatic(Base64.class); LoginResponse resp = httpAgent.urlCanBeAccessWithGivenCredentials("http://www.mocky.io/v2/5e54de89310000d559eb33d9", "", ""); Assert.assertEquals(LoginResponse.SUCCESS.message(), resp.message()); } @Test - public void testUrlCanBeAccessWithGivenCredentialsGivenWrongUrl(){ + public void testUrlCanBeAccessWithGivenCredentialsGivenWrongUrl() { PowerMockito.mockStatic(Base64.class); LoginResponse resp = httpAgent.urlCanBeAccessWithGivenCredentials("wrong.url", "", ""); Assert.assertEquals(LoginResponse.MALFORMED_URL.message(), resp.message()); @@ -105,27 +147,27 @@ public void testUrlCanBeAccessWithGivenCredentialsGivenWrongUrl(){ @Test @Ignore - public void testUrlCanBeAccessWithGivenCredentialsGivenEmptyResp(){ + public void testUrlCanBeAccessWithGivenCredentialsGivenEmptyResp() { PowerMockito.mockStatic(Base64.class); LoginResponse resp = httpAgent.urlCanBeAccessWithGivenCredentials("http://mockbin.org/bin/e42f7256-18b2-40b9-a20c-40fdc564d06f", "", ""); Assert.assertEquals(LoginResponse.SUCCESS_WITH_EMPTY_RESPONSE.message(), resp.message()); } @Test - public void testfetchWithCredentialsFailsGivenWrongUrl(){ - Response resp = httpAgent.fetchWithCredentials("wrong.url", "", ""); + public void testfetchWithCredentialsFailsGivenWrongUrl() { + Response resp = httpAgent.fetchWithCredentials("wrong.url"); Assert.assertEquals(ResponseStatus.failure, resp.status()); } @Test - public void testfetchWithCredentialsPassesGivenCorrectUrl(){ + public void testfetchWithCredentialsPassesGivenCorrectUrl() { PowerMockito.mockStatic(Base64.class); - Response resp = httpAgent.fetchWithCredentials("https://google.com", "", ""); + Response resp = httpAgent.fetchWithCredentials("https://google.com"); Assert.assertEquals(ResponseStatus.success, resp.status()); } @Test - public void testHttpImagePostGivenWrongUrl(){ + public void testHttpImagePostGivenWrongUrl() { String resp = httpAgent.httpImagePost("wrong.url", profileImage); Assert.assertEquals("", resp); } @@ -144,7 +186,7 @@ public void testHttpImagePostTimeout() { } @Test - public void testPostWithJsonResponse(){ + public void testPostWithJsonResponse() { PowerMockito.mockStatic(Base64.class); HashMap map = new HashMap<>(); map.put("title", "OpenSRP Testing Tuesdays"); diff --git a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java index 63387dd84..315fdf46b 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java @@ -211,7 +211,7 @@ public void logoutCurrentUser() { userService.remoteLogin("user X", "password Y", userInfo); - verify(allSettings).registerANM("user X", "password Y"); + verify(allSettings).registerANM("user X"); verify(session).setPassword("password Y"); } @@ -227,7 +227,7 @@ public void shouldDeleteDataAndSettingsWhenLogoutHappens() throws Exception { verify(repository).deleteRepository(); verify(repository).deleteRepository(); verify(allSettings).savePreviousFetchIndex("0"); - verify(allSettings).registerANM("", ""); + verify(allSettings).registerANM(""); } @Test diff --git a/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java b/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java index ac9d4affb..61eff77ad 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java @@ -223,6 +223,7 @@ public void setSearchTermInitsCorrectValue() { } @Test + @Ignore public void assertOnQRCodeSucessfullyScannedInvokesFilterWithCorrectParams() { String OPENSRP_ID = "8232-372-8L"; @@ -251,6 +252,7 @@ public void assertOnQRCodeSucessfullyScannedInvokesFilterWithCorrectParams() { } @Test + @Ignore public void assertOnQRCodeSucessfullyScannedInvokessetUniqueIDWithCorrectParams() { String OPENSRP_ID = "8232-372-8L"; From 721420632c4e2d097074b390ea117706214f8f98 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 12 May 2020 21:19:21 +0300 Subject: [PATCH 08/70] Code clean up --- opensrp-app/src/main/java/org/smartregister/Context.java | 3 +-- .../org/smartregister/account/AccountAuthenticator.java | 3 +-- .../main/java/org/smartregister/account/AccountError.java | 8 ++++++++ .../main/java/org/smartregister/service/HTTPAgent.java | 7 +------ .../sync/helper/SyncSettingsServiceHelper.java | 2 -- .../smartregister/view/activity/BaseLoginActivity.java | 7 ++++--- .../org/smartregister/view/activity/LoginActivity.java | 1 - .../java/org/smartregister/service/HTTPAgentTest.java | 2 +- 8 files changed, 16 insertions(+), 17 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/Context.java b/opensrp-app/src/main/java/org/smartregister/Context.java index 866c3499f..2ea9f16c9 100755 --- a/opensrp-app/src/main/java/org/smartregister/Context.java +++ b/opensrp-app/src/main/java/org/smartregister/Context.java @@ -520,8 +520,7 @@ public FormSubmissionSyncService formSubmissionSyncService() { public HTTPAgent httpAgent() { if (httpAgent == null) { - httpAgent = new HTTPAgent(applicationContext, allSettings(), allSharedPreferences(), - configuration()); + httpAgent = new HTTPAgent(applicationContext, allSharedPreferences(), configuration()); } return httpAgent; } diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java index 48d8158ac..abacdba00 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java @@ -19,7 +19,6 @@ */ public class AccountAuthenticator extends AbstractAccountAuthenticator { - private String TAG = AccountAuthenticator.class.getCanonicalName(); private final Context mContext; public AccountAuthenticator(Context context) { @@ -117,6 +116,6 @@ public Bundle updateCredentials(AccountAuthenticatorResponse response, Account a @Override public String getRefreshToken() { - return AccountHelper.getAccountManagerValue(AccountHelper.KEY_REFRESH_TOKEN,CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); + return AccountHelper.getAccountManagerValue(AccountHelper.KEY_REFRESH_TOKEN, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); } } diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountError.java b/opensrp-app/src/main/java/org/smartregister/account/AccountError.java index 87cefcd8a..6b972cbfa 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountError.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountError.java @@ -15,4 +15,12 @@ public AccountError(int statusCode, String error) { this.error = error; } + + public int getStatusCode() { + return statusCode; + } + + public String getError() { + return error; + } } diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index 251e3f1c5..3ddf4cc0e 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -29,7 +29,6 @@ import org.smartregister.domain.ResponseErrorStatus; import org.smartregister.domain.ResponseStatus; import org.smartregister.domain.jsonmapping.LoginResponseData; -import org.smartregister.repository.AllSettings; import org.smartregister.repository.AllSharedPreferences; import org.smartregister.ssl.OpensrpSSLHelper; import org.smartregister.util.SyncUtils; @@ -82,7 +81,6 @@ public class HTTPAgent { private Context context; - private AllSettings settings; private AllSharedPreferences allSharedPreferences; private DristhiConfiguration configuration; private GZIPCompression gzipCompression; @@ -98,10 +96,9 @@ public class HTTPAgent { private SyncUtils syncUtils; - public HTTPAgent(Context context, AllSettings settings, AllSharedPreferences + public HTTPAgent(Context context, AllSharedPreferences allSharedPreferences, DristhiConfiguration configuration) { this.context = context; - this.settings = settings; this.allSharedPreferences = allSharedPreferences; this.configuration = configuration; gzipCompression = new GZIPCompression(); @@ -831,9 +828,7 @@ private String refreshAuthenticationToken(String refreshToken) { AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); AccountManager accountManager = CoreLibrary.getInstance().getAccountManager(); - Account account = AccountHelper.getOauthAccountByType(authenticatorXml.getAccountType()); - String authToken = accountManager.peekAuthToken(account, AccountHelper.TOKEN_TYPE.PROVIDER); accountManager.setAuthToken(account, AccountHelper.TOKEN_TYPE.PROVIDER, response.getAccessToken()); accountManager.setPassword(account, response.getAccessToken()); diff --git a/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java b/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java index 293a7cc93..a3f5b51fd 100644 --- a/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java @@ -28,8 +28,6 @@ public class SyncSettingsServiceHelper { private HTTPAgent httpAgent; private String baseUrl; - private String username; - private String password; private AllSharedPreferences sharedPreferences; public SyncSettingsServiceHelper(String baseUrl, HTTPAgent httpAgent) { diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java index a2754f541..43b6cc123 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java @@ -64,6 +64,9 @@ protected void onCreate(Bundle savedInstanceState) { mLoginPresenter.setLanguage(); setupViews(mLoginPresenter); syncUtils = new SyncUtils(this); + + authTokenType = getIntent().getStringExtra(AccountHelper.INTENT_KEY.AUTH_TYPE); + } @Override @@ -292,15 +295,13 @@ public void showClearDataDialog(@NonNull DialogInterface.OnClickListener onClick public String getAuthTokenType() { - authTokenType = getIntent().getStringExtra(AccountHelper.INTENT_KEY.AUTH_TYPE); - if (authTokenType == null) authTokenType = AccountHelper.TOKEN_TYPE.PROVIDER; return authTokenType; } - public boolean isNewAccount(){ + public boolean isNewAccount() { return getIntent().getBooleanExtra(AccountHelper.INTENT_KEY.IS_NEW_ACCOUNT, false); } } \ No newline at end of file diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java index a676faae7..a18656057 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java @@ -1,6 +1,5 @@ package org.smartregister.view.activity; -import android.accounts.AccountAuthenticatorActivity; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index 8a9022051..c54f82a8f 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -96,7 +96,7 @@ public void setUp() { PowerMockito.mockStatic(AccountHelper.class); PowerMockito.when(AccountHelper.getOauthAccountByType(accountAuthenticatorXml.getAccountType())).thenReturn(account); - httpAgent = new HTTPAgent(context, allSettings, allSharedPreferences, dristhiConfiguration); + httpAgent = new HTTPAgent(context, allSharedPreferences, dristhiConfiguration); } @Test From 75290cf10f76b59957c48a8ae4f4486ddcbb90fd Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Thu, 14 May 2020 22:19:19 +0300 Subject: [PATCH 09/70] Refactor authentication mechanism to work for Keycloak --- .../account/AccountAuthenticator.java | 2 +- .../account/AccountConfiguration.java | 39 ++++ .../smartregister/account/AccountHelper.java | 10 + .../account/AccountResponse.java | 10 +- .../login/task/RemoteLoginTask.java | 64 ++++-- .../org/smartregister/service/HTTPAgent.java | 207 ++++++++++++++---- .../view/activity/DrishtiApplication.java | 6 +- 7 files changed, 266 insertions(+), 72 deletions(-) create mode 100644 opensrp-app/src/main/java/org/smartregister/account/AccountConfiguration.java diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java index abacdba00..229bd8449 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java @@ -59,7 +59,7 @@ public Bundle getAuthToken(AccountAuthenticatorResponse response, Account accoun Timber.d("Authenticate with saved credentials"); - AccountResponse accountResponse = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticate(account.name, password, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD); + AccountResponse accountResponse = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticate(account.name, password, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, CoreLibrary.getInstance().context().allSettings().get(AccountHelper.CONFIGURATION_CONSTANTS.TOKEN_ENDPOINT_URL)); authToken = accountResponse.getAccessToken(); refreshToken = accountResponse.getRefreshToken(); diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountConfiguration.java b/opensrp-app/src/main/java/org/smartregister/account/AccountConfiguration.java new file mode 100644 index 000000000..72aeabfad --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountConfiguration.java @@ -0,0 +1,39 @@ +package org.smartregister.account; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * Created by ndegwamartin on 14/05/2020. + */ +public class AccountConfiguration { + + @SerializedName("issuer") + private String issuerEndpoint; + + @SerializedName("authorization_endpoint") + private String authorizationEndpoint; + + @SerializedName("token_endpoint") + private String tokenEndpoint; + + @SerializedName("grant_types_supported") + private List grantTypesSupported; + + public String getIssuerEndpoint() { + return issuerEndpoint; + } + + public String getAuthorizationEndpoint() { + return authorizationEndpoint; + } + + public String getTokenEndpoint() { + return tokenEndpoint; + } + + public List getGrantTypesSupported() { + return grantTypesSupported; + } +} diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java index df0dda4bb..de97ca9c3 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java @@ -15,7 +15,17 @@ public class AccountHelper { public final static String KEY_REFRESH_TOKEN = "KEY_REFRESH_TOKEN"; public final static int MAX_AUTH_RETRIES = 1; + + public static final class CONFIGURATION_CONSTANTS { + + public final static String TOKEN_ENDPOINT_URL = "token_endpoint_url"; + public final static String AUTHORIZATION_ENDPOINT_URL = "authorization_endpoint_url"; + public final static String ISSUER_ENDPOINT_URL = "issuer_endpoint_url"; + } + public static final class OAUTH { + + public final static String ACCOUNT_CONFIGURATION_ENDPOINT = "/rest/config/keycloak"; public final static String TOKEN_ENDPOINT = "/oauth/token"; public static final class GRANT_TYPE { diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java b/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java index 0ece15cfc..9c7759cb9 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java @@ -16,9 +16,11 @@ public class AccountResponse { @SerializedName("refresh_token") private String refreshToken; + @SerializedName("refresh_expires_in") + private Integer refreshExpiresIn; @SerializedName("expires_in") - private Integer ExpiresIn; + private Integer expiresIn; @SerializedName("scope") private String Scope; @@ -53,10 +55,14 @@ public String getRefreshToken() { } public Integer getExpiresIn() { - return ExpiresIn; + return expiresIn; } public String getScope() { return Scope; } + + public Integer getRefreshExpiresIn() { + return refreshExpiresIn; + } } diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index 4dc1580ea..a23b1855f 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -2,6 +2,8 @@ import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.AccountsException; +import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; @@ -13,6 +15,7 @@ import org.smartregister.CoreLibrary; import org.smartregister.R; import org.smartregister.account.AccountAuthenticatorXml; +import org.smartregister.account.AccountConfiguration; import org.smartregister.account.AccountHelper; import org.smartregister.account.AccountResponse; import org.smartregister.domain.LoginResponse; @@ -58,41 +61,60 @@ protected LoginResponse doInBackground(Void... params) { Bundle userData = null; try { - AccountResponse response = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticate(mUsername, mPassword, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD); + AccountConfiguration accountConfiguration = CoreLibrary.getInstance().context().getHttpAgent().fetchOAuthConfiguration(); - AccountManager mAccountManager = CoreLibrary.getInstance().getAccountManager(); + if (accountConfiguration != null) { - final Account account = new Account(mUsername, mAccountAuthenticatorXml.getAccountType()); + if (!accountConfiguration.getGrantTypesSupported().contains(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD)) + throw new AccountsException("OAuth configuration DOES NOT support the Password Grant Type"); - loginResponse = getOpenSRPContext().userService().fetchUserDetails(response.getAccessToken()); + //Persist config resources + SharedPreferences.Editor sharedPrefEditor = CoreLibrary.getInstance().context().allSharedPreferences().getPreferences().edit(); - if (loginResponse != null && loginResponse.equals(LoginResponse.SUCCESS)) { + sharedPrefEditor.putString(AccountHelper.CONFIGURATION_CONSTANTS.TOKEN_ENDPOINT_URL, accountConfiguration.getTokenEndpoint()); + sharedPrefEditor.putString(AccountHelper.CONFIGURATION_CONSTANTS.AUTHORIZATION_ENDPOINT_URL, accountConfiguration.getAuthorizationEndpoint()); + sharedPrefEditor.putString(AccountHelper.CONFIGURATION_CONSTANTS.ISSUER_ENDPOINT_URL, accountConfiguration.getIssuerEndpoint()); + sharedPrefEditor.apply(); - userData = getOpenSRPContext().userService().saveUserGroup(mUsername, mPassword, loginResponse.payload()); + AccountResponse response = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticate(mUsername, mPassword, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, accountConfiguration.getTokenEndpoint()); - if (getOpenSRPContext().userService().getGroupId(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { + AccountManager mAccountManager = CoreLibrary.getInstance().getAccountManager(); + final Account account = new Account(mUsername, mAccountAuthenticatorXml.getAccountType()); - publishProgress(R.string.loading_client_settings); + loginResponse = getOpenSRPContext().userService().fetchUserDetails(response.getAccessToken()); + if (loginResponse != null && loginResponse.equals(LoginResponse.SUCCESS)) { - SyncSettingsServiceHelper syncSettingsServiceHelper = new SyncSettingsServiceHelper(getOpenSRPContext().configuration().dristhiBaseURL(), getOpenSRPContext().getHttpAgent()); - try { - JSONArray settings = pullSetting(syncSettingsServiceHelper, loginResponse); - JSONObject data = new JSONObject(); - data.put(AllConstants.PREF_KEY.SETTINGS, settings); - loginResponse.setRawData(data); - } catch (JSONException e) { - Timber.e(e); - } + userData = getOpenSRPContext().userService().saveUserGroup(mUsername, mPassword, loginResponse.payload()); + + if (getOpenSRPContext().userService().getGroupId(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { + + publishProgress(R.string.loading_client_settings); + + SyncSettingsServiceHelper syncSettingsServiceHelper = new SyncSettingsServiceHelper(getOpenSRPContext().configuration().dristhiBaseURL(), getOpenSRPContext().getHttpAgent()); + + try { + JSONArray settings = syncSettingsServiceHelper.pullSettingsFromServer(Utils.getFilterValue(loginResponse, CoreLibrary.getInstance().getSyncConfiguration().getSyncFilterParam())); + JSONObject prefSettingsData = new JSONObject(); + prefSettingsData.put(AllConstants.PREF_KEY.SETTINGS, settings); + loginResponse.setRawData(prefSettingsData); + + } catch (JSONException e) { + Timber.e(e); + } + + } } - } - mAccountManager.addAccountExplicitly(account, response.getAccessToken(), userData); - mAccountManager.setAuthToken(account, mLoginView.getAuthTokenType(), response.getAccessToken()); - mAccountManager.setUserData(account, AccountHelper.KEY_REFRESH_TOKEN, response.getRefreshToken()); + mAccountManager.addAccountExplicitly(account, response.getAccessToken(), userData); + mAccountManager.setAuthToken(account, mLoginView.getAuthTokenType(), response.getAccessToken()); + mAccountManager.setUserData(account, AccountHelper.KEY_REFRESH_TOKEN, response.getRefreshToken()); + } else { + throw new AccountsException("Could not fetch OAuth Configuration"); + } } catch (Exception e) { diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index 3ddf4cc0e..eae885525 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -11,6 +11,7 @@ import com.google.gson.Gson; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; import org.apache.http.util.ByteArrayBuffer; @@ -18,6 +19,7 @@ import org.smartregister.CoreLibrary; import org.smartregister.DristhiConfiguration; import org.smartregister.account.AccountAuthenticatorXml; +import org.smartregister.account.AccountConfiguration; import org.smartregister.account.AccountError; import org.smartregister.account.AccountHelper; import org.smartregister.account.AccountResponse; @@ -37,6 +39,7 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; +import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -91,6 +94,7 @@ public class HTTPAgent { private int connectTimeout = 60000; private int readTimeout = 60000; + private Gson gson; private static final String DETAILS_URL = "/user-details?anm-id="; @@ -101,6 +105,7 @@ public HTTPAgent(Context context, AllSharedPreferences this.context = context; this.allSharedPreferences = allSharedPreferences; this.configuration = configuration; + gson = new Gson(); gzipCompression = new GZIPCompression(); syncUtils = new SyncUtils(context.getApplicationContext()); } @@ -149,6 +154,9 @@ private HttpURLConnection httpURLConnectionTries(String requestURLPath) throws I if (urlConnection.getResponseCode() == HttpStatus.SC_UNAUTHORIZED) { refreshAuthenticationToken(AccountHelper.getAccountManagerValue(AccountHelper.KEY_REFRESH_TOKEN, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); + + } else { + break; } } @@ -242,9 +250,7 @@ public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, Strin Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", userName, url, e.toString()); loginResponse = NO_INTERNET_CONNECTIVITY; } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } + closeConnection(urlConnection); } return loginResponse; } @@ -306,9 +312,7 @@ private Response handleResponse(HttpURLConnection urlConnection) { Timber.e(e, "%s %s", NO_INTERNET_CONNECTIVITY, e.toString()); return new Response<>(ResponseStatus.failure, null); } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } + closeConnection(urlConnection); } return new Response<>(ResponseStatus.success, responseString).withTotalRecords(Utils.tryParseLong(totalRecords, 0)); } @@ -327,9 +331,11 @@ public String httpImagePost(String urlString, ProfileImage image) { PrintWriter writer; String responseString = ""; AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); + HttpURLConnection httpUrlConnection = null; try { - HttpURLConnection httpUrlConnection = initializeHttp(urlString); + + httpUrlConnection = initializeHttp(urlString); httpUrlConnection.setUseCaches(false); httpUrlConnection.setDoInput(true); @@ -375,7 +381,6 @@ public String httpImagePost(String urlString, ProfileImage image) { } reader.close(); } - httpUrlConnection.disconnect(); } catch (ProtocolException e) { Timber.e(e, "Protocol exception %s", e.toString()); @@ -385,6 +390,9 @@ public String httpImagePost(String urlString, ProfileImage image) { Timber.e(e, "MalformedUrl %s %s", MALFORMED_URL, e.toString()); } catch (IOException e) { Timber.e(e, "IOException %s %s", NO_INTERNET_CONNECTIVITY, e.toString()); + } finally { + + closeConnection(httpUrlConnection); } return responseString; } @@ -515,27 +523,45 @@ public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } - public AccountResponse oauth2authenticate(String username, String password, String grantType) { + public AccountResponse oauth2authenticate(String username, String password, String grantType, String tokenEndpointURL) { AccountError accountError = null; HttpURLConnection urlConnection = null; String url = null; - String baseURL = configuration.dristhiBaseURL() + AccountHelper.OAUTH.TOKEN_ENDPOINT; - String requestURL = baseURL + "?&grant_type=" + grantType + "&username=" + username + "&password=" + password; + OutputStream outputStream = null; + BufferedOutputStream writer = null; + try { - url = requestURL.replaceAll("\\s+", ""); - urlConnection = initializeHttp(url); + urlConnection = initializeHttp(tokenEndpointURL); String clientId = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientId(); String clientSecret = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientSecret(); final String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes()); + StringBuilder requestParamBuilder = new StringBuilder(); + requestParamBuilder.append("&grant_type=").append(grantType); + requestParamBuilder.append("&username=").append(username); + requestParamBuilder.append("&password=").append(password); + requestParamBuilder.append("&client_id=").append(clientId); + requestParamBuilder.append("&client_secret=").append(clientSecret); + + byte[] postData = requestParamBuilder.toString().getBytes(CharEncoding.UTF_8); + int postDataLength = postData.length; + + urlConnection.setDoOutput(true); + urlConnection.setInstanceFollowRedirects(false); urlConnection.setRequestMethod("POST"); - urlConnection.addRequestProperty("client_id", clientId); - urlConnection.addRequestProperty("client_secret", clientSecret); + urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + urlConnection.setRequestProperty("charset", "utf-8"); + urlConnection.setRequestProperty("Content-Length", Integer.toString(postDataLength)); urlConnection.setRequestProperty("Authorization", "Basic " + base64Auth); + urlConnection.setUseCaches(false); + outputStream = urlConnection.getOutputStream(); + writer = new BufferedOutputStream(outputStream); + writer.write(postData); + writer.flush(); int statusCode = urlConnection.getResponseCode(); InputStream inputStream; @@ -548,12 +574,12 @@ public AccountResponse oauth2authenticate(String username, String password, Stri Timber.d("response String: %s using request url %s", responseString, url); - AccountResponse accountResponse = new Gson().fromJson(responseString, AccountResponse.class); + AccountResponse accountResponse = gson.fromJson(responseString, AccountResponse.class); return accountResponse; } else { - accountError = new Gson().fromJson(responseString, AccountError.class); + accountError = gson.fromJson(responseString, AccountError.class); return new AccountResponse(statusCode, accountError); } @@ -572,9 +598,10 @@ public AccountResponse oauth2authenticate(String username, String password, Stri accountError = new AccountError(0, NO_INTERNET_CONNECTIVITY.name()); } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } + + closeConnection(urlConnection); + closeIOStream(writer); + closeIOStream(outputStream); } @@ -630,9 +657,8 @@ public LoginResponse fetchUserDetails(String requestURL, String oauthAccessToken Timber.e(e, "Failed to connect to %s check, check internet connection. Error: %s", url, e.toString()); loginResponse = NO_INTERNET_CONNECTIVITY; } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } + + closeConnection(urlConnection); } return loginResponse; } @@ -644,7 +670,8 @@ public LoginResponse fetchUserDetails(String requestURL, String oauthAccessToken * @return Response This returns whether the download succeeded or failed. */ public Response downloadFromURL(String downloadURL_, String fileName, String accessToken) { - HttpURLConnection httpUrlConnection; + + HttpURLConnection httpUrlConnection = null; try { File dir = new File(FormPathService.sdcardPathDownload); @@ -704,7 +731,6 @@ public Response downloadFromURL(String downloadURL_, String file Log.d("DownloadFormService", "download finished in " + ((System.currentTimeMillis() - startTime) / 1000) + " sec"); - httpUrlConnection.disconnect(); } else { Log.d("RESPONSE", "Server returned non-OK status: " + status); @@ -714,6 +740,9 @@ public Response downloadFromURL(String downloadURL_, String file } catch (IOException e) { Log.d("DownloadFormService", "download error : " + e); return new Response(ResponseStatus.success, DownloadStatus.failedDownloaded); + } finally { + + closeConnection(httpUrlConnection); } return new Response(ResponseStatus.success, DownloadStatus.downloaded); @@ -727,15 +756,18 @@ public boolean verifyAuthorization() { } final String username = allSharedPreferences.fetchRegisteredANM(); baseUrl = baseUrl + DETAILS_URL + username; + + HttpURLConnection urlConnection = null; + try { URL url = new URL(baseUrl); - HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection = (HttpURLConnection) url.openConnection(); AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); if (AccountHelper.getOauthAccountByType(authenticatorXml.getAccountType()) != null) urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); int statusCode = urlConnection.getResponseCode(); - urlConnection.disconnect(); + if (statusCode == HttpStatus.SC_UNAUTHORIZED) { Timber.i("User not authorized. User access was revoked, will log off user"); return false; @@ -747,6 +779,9 @@ public boolean verifyAuthorization() { } catch (IOException e) { Timber.e(e); + } finally { + + closeConnection(urlConnection); } return true; } @@ -755,28 +790,44 @@ public AccountResponse oauth2authenticateRefreshToken(String refreshToken) { AccountError accountError = null; HttpURLConnection urlConnection = null; - String url = null; + OutputStream outputStream = null; + BufferedOutputStream writer = null; + InputStream inputStream = null; String clientId = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientId(); String clientSecret = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientSecret(); - - String baseURL = configuration.dristhiBaseURL() + AccountHelper.OAUTH.TOKEN_ENDPOINT; - String requestURL = baseURL + "?&grant_type=" + AccountHelper.OAUTH.GRANT_TYPE.REFRESH_TOKEN + "&refresh_token=" + refreshToken + "&client_id=" + clientId; + String tokenEndpointURL = null; try { - url = requestURL.replaceAll("\\s+", ""); - urlConnection = initializeHttp(url); + tokenEndpointURL = CoreLibrary.getInstance().context().allSharedPreferences().getPreferences().getString(AccountHelper.CONFIGURATION_CONSTANTS.TOKEN_ENDPOINT_URL, ""); + urlConnection = initializeHttp(tokenEndpointURL); final String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes()); + StringBuilder requestParamBuilder = new StringBuilder(); + requestParamBuilder.append("&grant_type=").append(AccountHelper.OAUTH.GRANT_TYPE.REFRESH_TOKEN); + requestParamBuilder.append("&refresh_token=").append(refreshToken); + requestParamBuilder.append("&client_id=").append(clientId); + requestParamBuilder.append("&client_secret=").append(clientSecret); + + byte[] postData = requestParamBuilder.toString().getBytes(CharEncoding.UTF_8); + int postDataLength = postData.length; + + urlConnection.setDoOutput(true); + urlConnection.setInstanceFollowRedirects(false); urlConnection.setRequestMethod("POST"); - urlConnection.addRequestProperty("client_id", clientId); - urlConnection.addRequestProperty("client_secret", clientSecret); - urlConnection.setRequestProperty("Authorization", "Bearer " + base64Auth); + urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + urlConnection.setRequestProperty("charset", "utf-8"); + urlConnection.setRequestProperty("Content-Length", Integer.toString(postDataLength)); + urlConnection.setRequestProperty("Authorization", "Basic " + base64Auth); + urlConnection.setUseCaches(false); + outputStream = urlConnection.getOutputStream(); + writer = new BufferedOutputStream(outputStream);//Bearer + writer.write(postData); + writer.flush(); int statusCode = urlConnection.getResponseCode(); - InputStream inputStream; if (statusCode >= HttpStatus.SC_BAD_REQUEST) inputStream = urlConnection.getErrorStream(); else @@ -784,19 +835,19 @@ public AccountResponse oauth2authenticateRefreshToken(String refreshToken) { String responseString = IOUtils.toString(inputStream); if (statusCode == HttpStatus.SC_OK) { - Timber.d("response String: %s using request url %s", responseString, url); + Timber.d("response String: %s using request url %s", responseString, tokenEndpointURL); - AccountResponse accountResponse = new Gson().fromJson(responseString, AccountResponse.class); + AccountResponse accountResponse = gson.fromJson(responseString, AccountResponse.class); return accountResponse; } else { - accountError = new Gson().fromJson(responseString, AccountError.class); + accountError = gson.fromJson(responseString, AccountError.class); return new AccountResponse(statusCode, accountError); } } catch (MalformedURLException e) { - Timber.e(e, "Failed to check credentials bad url %s", url); + Timber.e(e, "Failed to check credentials bad url %s", tokenEndpointURL); accountError = new AccountError(0, MALFORMED_URL.name()); } catch (SocketTimeoutException e) { @@ -804,15 +855,15 @@ public AccountResponse oauth2authenticateRefreshToken(String refreshToken) { accountError = new AccountError(0, TIMEOUT.name()); - Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", refreshToken, url, e.toString()); + Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", refreshToken, tokenEndpointURL, e.toString()); } catch (IOException e) { - Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", refreshToken, url, e.toString()); + Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", refreshToken, tokenEndpointURL, e.toString()); accountError = new AccountError(0, NO_INTERNET_CONNECTIVITY.name()); } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } + closeConnection(urlConnection); + closeIOStream(writer); + closeIOStream(outputStream); } @@ -836,4 +887,68 @@ private String refreshAuthenticationToken(String refreshToken) { return response.getAccessToken(); } + + public AccountConfiguration fetchOAuthConfiguration() { + + String baseUrl = configuration.dristhiBaseURL(); + + if (baseUrl.endsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + } + + baseUrl = baseUrl + AccountHelper.OAUTH.ACCOUNT_CONFIGURATION_ENDPOINT; + + HttpURLConnection urlConnection = null; + + InputStream inputStream = null; + try { + URL url = new URL(baseUrl); + urlConnection = (HttpURLConnection) url.openConnection(); + + int statusCode = urlConnection.getResponseCode(); + if (statusCode == HttpStatus.SC_OK) { + + inputStream = urlConnection.getInputStream(); + + String responseString = IOUtils.toString(inputStream); + + return gson.fromJson(responseString, AccountConfiguration.class); + } + + } catch (IOException e) { + Timber.e(e); + } finally { + + closeConnection(urlConnection); + closeIOStream(inputStream); + + } + return null; + } + + private void closeConnection(HttpURLConnection urlConnection) { + if (urlConnection != null) { + try { + urlConnection.disconnect(); + } catch (Exception ex) { + Timber.e(ex, "Error closing input HttpUrlConnection"); + } + + } + + } + + private void closeIOStream(Closeable inputStream) { + if (inputStream != null) { + + try { + inputStream.close(); + } catch (IOException ex) { + + Timber.e(ex, "Error closing input stream"); + } + + } + + } } \ No newline at end of file diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java index 01262dc70..e463bda49 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java @@ -14,6 +14,7 @@ import org.smartregister.Context; import org.smartregister.CoreLibrary; import org.smartregister.R; +import org.smartregister.account.AccountHelper; import org.smartregister.repository.DrishtiRepository; import org.smartregister.repository.Repository; import org.smartregister.sync.ClientProcessorForJava; @@ -122,8 +123,9 @@ public Repository getRepository() { public String getPassword() { if (password == null) { - String username = context.userService().getAllSharedPreferences().fetchRegisteredANM(); - password = context.userService().getGroupId(username); + + password = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); + } return password; } From b1eeee1d6ac9d9f7644fe3766cb6f87351d6d138 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Fri, 15 May 2020 15:22:28 +0300 Subject: [PATCH 10/70] Remove all stored password references --- .../repository/AllSharedPreferences.java | 14 --------- .../smartregister/service/UserService.java | 25 ++++------------ .../view/activity/DrishtiApplication.java | 6 ++-- .../repository/AllSharedPreferencesTest.java | 29 +++++-------------- 4 files changed, 17 insertions(+), 57 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java index cea320ed8..8647aee7c 100644 --- a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java @@ -16,7 +16,6 @@ import static org.smartregister.AllConstants.DEFAULT_TEAM_PREFIX; import static org.smartregister.AllConstants.DRISHTI_BASE_URL; import static org.smartregister.AllConstants.ENCRYPTED_GROUP_ID_PREFIX; -import static org.smartregister.AllConstants.ENCRYPTED_PASSWORD_PREFIX; import static org.smartregister.AllConstants.FORCE_REMOTE_LOGIN; import static org.smartregister.AllConstants.IS_SYNC_INITIAL_KEY; import static org.smartregister.AllConstants.IS_SYNC_IN_PROGRESS_PREFERENCE_KEY; @@ -73,19 +72,6 @@ public void saveServerTimeZone(String serverTimeZone) { preferences.edit().putString(SERVER_TIMEZONE, serverTimeZone).commit(); } - public String fetchEncryptedPassword(String username) { - if (username != null) { - return preferences.getString(ENCRYPTED_PASSWORD_PREFIX + username, null); - } - return null; - } - - public void saveEncryptedPassword(String username, String password) { - if (username != null) { - preferences.edit().putString(ENCRYPTED_PASSWORD_PREFIX + username, password).commit(); - } - } - public String fetchPioneerUser() { return preferences.getString(PIONEER_USER, null); } diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 6ec940e33..117aa9738 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -214,22 +214,14 @@ public boolean isValidLocalLogin(String userName, String password) { public boolean isUserInValidGroup(final String userName, final String password) { // Check if everything OK for local login - if (keyStore != null && userName != null && password != null && !allSharedPreferences - .fetchForceRemoteLogin()) { + if (keyStore != null && userName != null && password != null && !allSharedPreferences.fetchForceRemoteLogin()) { String username = userName.equalsIgnoreCase(allSharedPreferences.fetchRegisteredANM()) ? allSharedPreferences.fetchRegisteredANM() : userName; try { KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(username); if (privateKeyEntry != null) { - // Compare stored encrypted password with provided password - String encryptedPassword = allSharedPreferences. - fetchEncryptedPassword(username); - String decryptedPassword = decryptString(privateKeyEntry, encryptedPassword); - - if (password.equals(decryptedPassword)) { - String groupId = getGroupId(username, privateKeyEntry); - if (groupId != null) { - return isValidGroupId(groupId); - } + String groupId = getGroupId(username, privateKeyEntry); + if (groupId != null) { + return isValidGroupId(groupId); } } } catch (Exception e) { @@ -587,7 +579,6 @@ public Bundle saveUserGroup(String userName, String password, LoginResponseData return null; } - String groupId = null; SyncConfiguration syncConfiguration = CoreLibrary.getInstance().getSyncConfiguration(); @@ -604,20 +595,16 @@ public Bundle saveUserGroup(String userName, String password, LoginResponseData if (StringUtils.isBlank(groupId)) { return null; - } else { - - bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, groupId); } if (privateKeyEntry != null) { - // First save the encrypted password - String encryptedPassword = encryptString(privateKeyEntry, password); - allSharedPreferences.saveEncryptedPassword(username, encryptedPassword); // Then save the encrypted group String encryptedGroupId = encryptString(privateKeyEntry, groupId); allSharedPreferences.saveEncryptedGroupId(username, encryptedGroupId); + bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, encryptedGroupId); + // Finally, save the pioneer user if (allSharedPreferences.fetchPioneerUser() == null) { allSharedPreferences.savePioneerUser(username); diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java index e463bda49..d548dc714 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java @@ -14,7 +14,6 @@ import org.smartregister.Context; import org.smartregister.CoreLibrary; import org.smartregister.R; -import org.smartregister.account.AccountHelper; import org.smartregister.repository.DrishtiRepository; import org.smartregister.repository.Repository; import org.smartregister.sync.ClientProcessorForJava; @@ -124,9 +123,10 @@ public Repository getRepository() { public String getPassword() { if (password == null) { - password = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); - + String username = context.userService().getAllSharedPreferences().fetchRegisteredANM(); + password = context.userService().getGroupId(username); } + return password; } diff --git a/opensrp-app/src/test/java/org/smartregister/repository/AllSharedPreferencesTest.java b/opensrp-app/src/test/java/org/smartregister/repository/AllSharedPreferencesTest.java index e584bbfe4..0aeed77cf 100644 --- a/opensrp-app/src/test/java/org/smartregister/repository/AllSharedPreferencesTest.java +++ b/opensrp-app/src/test/java/org/smartregister/repository/AllSharedPreferencesTest.java @@ -74,19 +74,6 @@ public void assertSaveServerTimeZone() { Mockito.verify(preferences, Mockito.times(1)).edit(); } - @Test - public void assertFetchEncryptedPassword() { - Assert.assertNull(allSharedPreferences.fetchEncryptedPassword(null)); - Assert.assertNotNull(allSharedPreferences.fetchEncryptedPassword("")); - Assert.assertEquals(allSharedPreferences.fetchEncryptedPassword(""), str); - } - - @Test - public void assertSaveEncryptedPassword() { - allSharedPreferences.saveEncryptedPassword("uname", "pword"); - Mockito.verify(preferences, Mockito.times(1)).edit(); - } - @Test public void assertFetchPioneerUser() { Assert.assertEquals(allSharedPreferences.fetchPioneerUser(), str); @@ -309,7 +296,7 @@ public void testFetchDefaultTeamId() { @Test public void testFetchLastSyncDate() { - Mockito.when(preferences.getLong(any(), anyLong())).thenReturn(2000L); + Mockito.when(preferences.getLong(any(), anyLong())).thenReturn(2000L); assertEquals((Long) 2000L, allSharedPreferences.fetchLastSyncDate(1000L)); } @@ -340,7 +327,7 @@ public void testSaveIsSyncInitial() { @Test public void testFetchLastCheckTimeStamp() { - Mockito.when(preferences.getLong(any(), anyLong())).thenReturn(2000L); + Mockito.when(preferences.getLong(any(), anyLong())).thenReturn(2000L); assertEquals(2000L, allSharedPreferences.fetchLastCheckTimeStamp()); } @@ -371,14 +358,14 @@ public void testUpdateLastSettingsSyncTimeStamp() { @Test public void testFetchLastSettingsSyncTimeStamp() { - Mockito.when(preferences.getLong(any(), anyLong())).thenReturn(2000L); + Mockito.when(preferences.getLong(any(), anyLong())).thenReturn(2000L); assertEquals(2000L, allSharedPreferences.fetchLastSettingsSyncTimeStamp()); } @Test public void testIsMigratedToSqlite4() { - Mockito.when(preferences.getBoolean(any(), anyBoolean())).thenReturn(true); + Mockito.when(preferences.getBoolean(any(), anyBoolean())).thenReturn(true); assertTrue(allSharedPreferences.isMigratedToSqlite4()); } @@ -397,7 +384,7 @@ public void testSetMigratedToSqlite4() { @Test public void testGetLastPeerToPeerSyncProcessedEvent() { - Mockito.when(preferences.getInt(any(), anyInt())).thenReturn(10); + Mockito.when(preferences.getInt(any(), anyInt())).thenReturn(10); assertEquals(10, allSharedPreferences.getLastPeerToPeerSyncProcessedEvent()); } @@ -416,7 +403,7 @@ public void testSetLastPeerToPeerSyncProcessedEvent() { @Test public void isPeerToPeerUnprocessedEvents() { - Mockito.when(preferences.getBoolean(any(), anyBoolean())).thenReturn(true); + Mockito.when(preferences.getBoolean(any(), anyBoolean())).thenReturn(true); assertTrue(allSharedPreferences.isPeerToPeerUnprocessedEvents()); } @@ -447,7 +434,7 @@ public void testUpdateLastClientProcessedTimeStamp() { @Test public void testFetchLastClientProcessedTimeStamp() { - Mockito.when(preferences.getLong(any(), anyLong())).thenReturn(2000L); + Mockito.when(preferences.getLong(any(), anyLong())).thenReturn(2000L); assertEquals(2000L, allSharedPreferences.fetchLastClientProcessedTimeStamp()); } @@ -466,7 +453,7 @@ public void testUpdateTransactionsKilledFlag() { @Test public void testFetchTransactionsKilledFlag() { - Mockito.when(preferences.getBoolean(any(), anyBoolean())).thenReturn(true); + Mockito.when(preferences.getBoolean(any(), anyBoolean())).thenReturn(true); assertTrue(allSharedPreferences.fetchTransactionsKilledFlag()); } From 60a1c78fccc227261316e351057b94c8c4c5e9c2 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Sat, 16 May 2020 09:51:17 +0300 Subject: [PATCH 11/70] Fix error dialog rendering multiple times at a go --- .../view/activity/BaseLoginActivity.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java index 43b6cc123..cb45af0dc 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java @@ -51,6 +51,7 @@ public abstract class BaseLoginActivity extends MultiLanguageActivity implements private Boolean showPasswordChecked = false; private SyncUtils syncUtils; private String authTokenType; + private AlertDialog alertDialog; @Override protected void onCreate(Bundle savedInstanceState) { @@ -166,16 +167,19 @@ public void showErrorDialog(String message) { } public void showErrorDialog(@StringRes int title, String message) { - AlertDialog alertDialog = new AlertDialog.Builder(this) - .setTitle(title) - .setMessage(message) - .setPositiveButton("OK", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - } - }) - .create(); + + if (alertDialog == null) { + alertDialog = new AlertDialog.Builder(this) + .setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + } + }).create(); + } + + alertDialog.setTitle(title); + alertDialog.setMessage(message); alertDialog.show(); } From 4e2823378363582c6595ac425e43625297a61cd7 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 19 May 2020 11:17:50 +0300 Subject: [PATCH 12/70] Fix bug missing tag level on location hierarchy --- .../java/org/smartregister/location/helper/LocationHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opensrp-app/src/main/java/org/smartregister/location/helper/LocationHelper.java b/opensrp-app/src/main/java/org/smartregister/location/helper/LocationHelper.java index a8993bb5f..0e0839ec9 100644 --- a/opensrp-app/src/main/java/org/smartregister/location/helper/LocationHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/location/helper/LocationHelper.java @@ -471,7 +471,7 @@ private List getFormJsonData(TreeNode openMrsLoc formLocation.key = name; Set levels = node.getTags(); - formLocation.level = ""; + formLocation.level = levels != null && !levels.isEmpty() ? levels.iterator().next() : ""; LinkedHashMap> childMap = childMap(openMrsLocationData); From 47ffabfeeeb9df0d17b2d3c5650f3cba776eaafe Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 26 May 2020 09:47:16 +0300 Subject: [PATCH 13/70] Update OAuth implementation - Refactor refresh token and verify login logic - Add support for Spring OAuth Fallback if keycloak not supported --- opensrp-app/AndroidManifest.xml | 1 + .../org/smartregister/SyncConfiguration.java | 3 + .../account/AbstractAccountAuthenticator.java | 14 -- .../account/AccountAuthenticator.java | 24 +-- .../account/AccountConfiguration.java | 16 ++ .../smartregister/account/AccountHelper.java | 30 ++- .../login/interactor/BaseLoginInteractor.java | 12 +- .../login/task/RemoteLoginTask.java | 25 ++- .../org/smartregister/service/HTTPAgent.java | 193 +++++++++--------- .../smartregister/service/UserService.java | 51 ++--- .../helper/SyncSettingsServiceHelper.java | 28 +-- .../view/activity/DrishtiApplication.java | 9 +- .../view/activity/LoginActivity.java | 12 +- .../smartregister/TestSyncConfiguration.java | 7 + .../smartregister/service/HTTPAgentTest.java | 4 +- .../service/UserServiceTest.java | 3 +- .../view/activity/LoginActivityTest.java | 2 +- .../LoginActivityWithRemoteLoginTest.java | 2 +- .../fragment/BaseRegisterFragmentTest.java | 4 + 19 files changed, 234 insertions(+), 206 deletions(-) delete mode 100644 opensrp-app/src/main/java/org/smartregister/account/AbstractAccountAuthenticator.java diff --git a/opensrp-app/AndroidManifest.xml b/opensrp-app/AndroidManifest.xml index 4428271db..168741189 100644 --- a/opensrp-app/AndroidManifest.xml +++ b/opensrp-app/AndroidManifest.xml @@ -16,6 +16,7 @@ + diff --git a/opensrp-app/src/main/java/org/smartregister/SyncConfiguration.java b/opensrp-app/src/main/java/org/smartregister/SyncConfiguration.java index 290404ad9..cbc33477e 100644 --- a/opensrp-app/src/main/java/org/smartregister/SyncConfiguration.java +++ b/opensrp-app/src/main/java/org/smartregister/SyncConfiguration.java @@ -1,6 +1,7 @@ package org.smartregister; import org.smartregister.account.AccountHelper; +import org.smartregister.view.activity.BaseLoginActivity; import java.util.ArrayList; import java.util.List; @@ -181,4 +182,6 @@ public boolean runPlanEvaluationOnClientProcessing() { public int getMaxAuthenticationRetries() { return AccountHelper.MAX_AUTH_RETRIES; } + + public abstract Class getAuthenticationActivity(); } diff --git a/opensrp-app/src/main/java/org/smartregister/account/AbstractAccountAuthenticator.java b/opensrp-app/src/main/java/org/smartregister/account/AbstractAccountAuthenticator.java deleted file mode 100644 index 5497ef7ba..000000000 --- a/opensrp-app/src/main/java/org/smartregister/account/AbstractAccountAuthenticator.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.smartregister.account; - -import android.content.Context; - -/** - * Created by ndegwamartin on 11/05/2020. - */ -public abstract class AbstractAccountAuthenticator extends android.accounts.AbstractAccountAuthenticator { - public AbstractAccountAuthenticator(Context context) { - super(context); - } - - public abstract String getRefreshToken(); -} diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java index 229bd8449..0c2a75bdb 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java @@ -1,5 +1,6 @@ package org.smartregister.account; +import android.accounts.AbstractAccountAuthenticator; import android.accounts.Account; import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountManager; @@ -9,8 +10,8 @@ import android.os.Bundle; import android.text.TextUtils; +import org.apache.http.HttpStatus; import org.smartregister.CoreLibrary; -import org.smartregister.view.activity.LoginActivity; import timber.log.Timber; @@ -30,7 +31,7 @@ public AccountAuthenticator(Context context) { @Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { - final Intent intent = new Intent(mContext, LoginActivity.class); + final Intent intent = new Intent(mContext, CoreLibrary.getInstance().getSyncConfiguration().getAuthenticationActivity()); intent.putExtra(AccountHelper.INTENT_KEY.ACCOUNT_TYPE, accountType); intent.putExtra(AccountHelper.INTENT_KEY.AUTH_TYPE, authTokenType); intent.putExtra(AccountHelper.INTENT_KEY.IS_NEW_ACCOUNT, true); @@ -59,9 +60,14 @@ public Bundle getAuthToken(AccountAuthenticatorResponse response, Account accoun Timber.d("Authenticate with saved credentials"); - AccountResponse accountResponse = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticate(account.name, password, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, CoreLibrary.getInstance().context().allSettings().get(AccountHelper.CONFIGURATION_CONSTANTS.TOKEN_ENDPOINT_URL)); - authToken = accountResponse.getAccessToken(); - refreshToken = accountResponse.getRefreshToken(); + AccountResponse accountResponse = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticateRefreshToken(password); + if (accountResponse.getStatus() == HttpStatus.SC_OK) { + authToken = accountResponse.getAccessToken(); + refreshToken = accountResponse.getRefreshToken(); + + accountManager.setPassword(account, refreshToken); + accountManager.setAuthToken(account, authTokenType, authToken); + } } catch (Exception e) { Timber.e(e); @@ -74,11 +80,10 @@ public Bundle getAuthToken(AccountAuthenticatorResponse response, Account accoun result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); result.putString(AccountManager.KEY_AUTHTOKEN, authToken); - result.putString(AccountHelper.KEY_REFRESH_TOKEN, refreshToken); return result; } - final Intent intent = new Intent(mContext, LoginActivity.class); + final Intent intent = new Intent(mContext, CoreLibrary.getInstance().getSyncConfiguration().getAuthenticationActivity()); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); intent.putExtra(AccountHelper.INTENT_KEY.ACCOUNT_TYPE, account.type); intent.putExtra(AccountHelper.INTENT_KEY.AUTH_TYPE, authTokenType); @@ -113,9 +118,4 @@ public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { return null; } - - @Override - public String getRefreshToken() { - return AccountHelper.getAccountManagerValue(AccountHelper.KEY_REFRESH_TOKEN, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); - } } diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountConfiguration.java b/opensrp-app/src/main/java/org/smartregister/account/AccountConfiguration.java index 72aeabfad..171d9a47b 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountConfiguration.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountConfiguration.java @@ -36,4 +36,20 @@ public String getTokenEndpoint() { public List getGrantTypesSupported() { return grantTypesSupported; } + + public void setIssuerEndpoint(String issuerEndpoint) { + this.issuerEndpoint = issuerEndpoint; + } + + public void setAuthorizationEndpoint(String authorizationEndpoint) { + this.authorizationEndpoint = authorizationEndpoint; + } + + public void setTokenEndpoint(String tokenEndpoint) { + this.tokenEndpoint = tokenEndpoint; + } + + public void setGrantTypesSupported(List grantTypesSupported) { + this.grantTypesSupported = grantTypesSupported; + } } diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java index de97ca9c3..4a0b4a06b 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java @@ -5,6 +5,8 @@ import org.smartregister.CoreLibrary; +import timber.log.Timber; + /** * Created by ndegwamartin on 2020-04-27. */ @@ -64,7 +66,7 @@ public static Account getOauthAccountByType(String accountType) { public static String getAccountManagerValue(String key, String accountType) { Account account = AccountHelper.getOauthAccountByType(accountType); if (account != null) { - return CoreLibrary.getInstance().getAccountManager().getUserData(account, key); + return accountManager.getUserData(account, key); } return null; } @@ -72,11 +74,35 @@ public static String getAccountManagerValue(String key, String accountType) { /** * @param accountType unique name to identify our account type in the Account Manager * @param authTokenType type of token requested from server e.g. PROVIDER, ADMIN - * @return + * @return access token */ public static String getOAuthToken(String accountType, String authTokenType) { Account account = getOauthAccountByType(accountType); + try { + return accountManager.blockingGetAuthToken(account, authTokenType, true); + } catch (Exception ex) { + Timber.e(ex, "EXCEPTION: %s", ex.toString()); + return null; + } + } + + /** This method invalidates the auth token so that the Authenticator can fetch a new one from server + * @param accountType unique name to identify our account type in the Account Manager + * @param authToken token to invalidate + */ + public static void invalidateAuthToken(String accountType, String authToken) { + accountManager.invalidateAuthToken(accountType, authToken); + } + + + /** + * @param accountType unique name to identify our account type in the Account Manager + * @param authTokenType type of token requested from server e.g. PROVIDER, ADMIN + * @return access token cached + */ + public static String getCachedOAuthToken(String accountType, String authTokenType) { + Account account = getOauthAccountByType(accountType); return accountManager.peekAuthToken(account, authTokenType); } diff --git a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java index e59d97d3e..fbca7463e 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java +++ b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java @@ -88,16 +88,16 @@ private void localLogin(WeakReference view, String userN } else if (isAuthenticated && (!AllConstants.TIME_CHECK || TimeStatus.OK.equals(getUserService().validateStoredServerTimeZone()))) { - navigateToHomePage(userName, password); + navigateToHomePage(userName); } else { loginWithLocalFlag(view, false, userName, password); } } - private void navigateToHomePage(String userName, String password) { + private void navigateToHomePage(String userName) { - getUserService().localLogin(userName, password); + getUserService().localLogin(userName); getLoginView().goToHome(false); CoreLibrary.getInstance().initP2pLibrary(userName); @@ -136,7 +136,7 @@ public void onEvent(LoginResponse loginResponse) { ); if (!AllConstants.TIME_CHECK || timeStatus.equals(TimeStatus.OK)) { - remoteLoginWith(username, password, loginResponse); + remoteLoginWith(username, loginResponse); } else { if (timeStatus.equals(TimeStatus.TIMEZONE_MISMATCH)) { @@ -207,8 +207,8 @@ private void tryRemoteLogin(final String userName, final String password, final remoteLoginTask.execute(); } - private void remoteLoginWith(String userName, String password, LoginResponse loginResponse) { - getUserService().remoteLogin(userName, password, loginResponse.payload()); + private void remoteLoginWith(String userName, LoginResponse loginResponse) { + getUserService().processLoginResponseDataForUser(userName, loginResponse.payload()); processServerSettings(loginResponse); scheduleJobsPeriodically(); diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index a23b1855f..6052d4b3e 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -24,6 +24,8 @@ import org.smartregister.util.Utils; import org.smartregister.view.contract.BaseLoginContract; +import java.util.Arrays; + import timber.log.Timber; import static org.smartregister.domain.LoginResponse.CUSTOM_SERVER_RESPONSE; @@ -58,11 +60,20 @@ protected void onPreExecute() { protected LoginResponse doInBackground(Void... params) { LoginResponse loginResponse; - Bundle userData = null; try { AccountConfiguration accountConfiguration = CoreLibrary.getInstance().context().getHttpAgent().fetchOAuthConfiguration(); + boolean isKeyclockConfigured = accountConfiguration != null; + + if (!isKeyclockConfigured) { + accountConfiguration = new AccountConfiguration(); + accountConfiguration.setGrantTypesSupported(Arrays.asList(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD)); + accountConfiguration.setTokenEndpoint(CoreLibrary.getInstance().context().configuration().dristhiBaseURL() + AccountHelper.OAUTH.TOKEN_ENDPOINT); + accountConfiguration.setAuthorizationEndpoint(""); + accountConfiguration.setIssuerEndpoint(""); + } + if (accountConfiguration != null) { if (!accountConfiguration.getGrantTypesSupported().contains(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD)) @@ -86,7 +97,11 @@ protected LoginResponse doInBackground(Void... params) { if (loginResponse != null && loginResponse.equals(LoginResponse.SUCCESS)) { - userData = getOpenSRPContext().userService().saveUserGroup(mUsername, mPassword, loginResponse.payload()); + Bundle userData = getOpenSRPContext().userService().saveUserGroup(mUsername, mPassword, loginResponse.payload()); + + mAccountManager.addAccountExplicitly(account, response.getRefreshToken(), userData); + mAccountManager.setAuthToken(account, mLoginView.getAuthTokenType(), response.getAccessToken()); + mAccountManager.setPassword(account, response.getRefreshToken()); if (getOpenSRPContext().userService().getGroupId(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { @@ -95,7 +110,7 @@ protected LoginResponse doInBackground(Void... params) { SyncSettingsServiceHelper syncSettingsServiceHelper = new SyncSettingsServiceHelper(getOpenSRPContext().configuration().dristhiBaseURL(), getOpenSRPContext().getHttpAgent()); try { - JSONArray settings = syncSettingsServiceHelper.pullSettingsFromServer(Utils.getFilterValue(loginResponse, CoreLibrary.getInstance().getSyncConfiguration().getSyncFilterParam())); + JSONArray settings = syncSettingsServiceHelper.pullSettingsFromServer(Utils.getFilterValue(loginResponse, CoreLibrary.getInstance().getSyncConfiguration().getSyncFilterParam()),response.getAccessToken()); JSONObject prefSettingsData = new JSONObject(); prefSettingsData.put(AllConstants.PREF_KEY.SETTINGS, settings); @@ -108,10 +123,6 @@ protected LoginResponse doInBackground(Void... params) { } } - mAccountManager.addAccountExplicitly(account, response.getAccessToken(), userData); - mAccountManager.setAuthToken(account, mLoginView.getAuthTokenType(), response.getAccessToken()); - mAccountManager.setUserData(account, AccountHelper.KEY_REFRESH_TOKEN, response.getRefreshToken()); - } else { throw new AccountsException("Could not fetch OAuth Configuration"); } diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index eae885525..9c8a2bdb8 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -1,7 +1,5 @@ package org.smartregister.service; -import android.accounts.Account; -import android.accounts.AccountManager; import android.content.Context; import android.support.annotation.NonNull; import android.util.Base64; @@ -110,7 +108,10 @@ public HTTPAgent(Context context, AllSharedPreferences syncUtils = new SyncUtils(context.getApplicationContext()); } - private HttpURLConnection initializeHttp(String requestURLPath) throws IOException { + /** + * This method initializes httpurlconnection + */ + private HttpURLConnection initializeHttp(String requestURLPath, boolean setOauthToken) throws IOException { URL url = new URL(requestURLPath); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); if (urlConnection instanceof HttpsURLConnection) { @@ -119,73 +120,60 @@ private HttpURLConnection initializeHttp(String requestURLPath) throws IOExcepti } urlConnection.setConnectTimeout(getConnectTimeout()); urlConnection.setReadTimeout(getReadTimeout()); - AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); - if (AccountHelper.getOauthAccountByType(authenticatorXml.getAccountType()) != null) - urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); - + if (setOauthToken) { + AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); + if (AccountHelper.getOauthAccountByType(authenticatorXml.getAccountType()) != null) + urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); + } return urlConnection; } public Response fetch(String requestURLPath) { try { - HttpURLConnection urlConnection = httpURLConnectionTries(requestURLPath); - - return handleResponse(urlConnection); - - } catch (IOException ex) { - Timber.e(ex, "EXCEPTION %s", ex.toString()); - return new Response<>(ResponseStatus.failure, null); - } - } - - @NonNull - private HttpURLConnection httpURLConnectionTries(String requestURLPath) throws IOException { - - int authRetries = 0; - HttpURLConnection urlConnection = null; - - while (authRetries < CoreLibrary.getInstance().getSyncConfiguration().getMaxAuthenticationRetries() + 1) { + HttpURLConnection urlConnection = initializeHttp(requestURLPath, true); + //If unauthorized, request new token - authRetries++; + if (HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode()) { - urlConnection = initializeHttp(requestURLPath); + AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); - if (urlConnection.getResponseCode() == HttpStatus.SC_UNAUTHORIZED) { + String authToken = AccountHelper.getCachedOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + if (authToken != null) + AccountHelper.invalidateAuthToken(authenticatorXml.getAccountType(), authToken); - refreshAuthenticationToken(AccountHelper.getAccountManagerValue(AccountHelper.KEY_REFRESH_TOKEN, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); + urlConnection = initializeHttp(requestURLPath, true); - } else { - break; } + return processResponse(urlConnection); + + } catch (IOException ex) { + Timber.e(ex, "EXCEPTION %s", ex.toString()); + return new Response<>(ResponseStatus.failure, null); } - return urlConnection; } public Response post(String postURLPath, String jsonPayload) { HttpURLConnection urlConnection; AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); try { - urlConnection = initializeHttp(postURLPath); - urlConnection.setDoOutput(true); - urlConnection.setRequestMethod("POST"); - urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8"); - urlConnection.setRequestProperty("Content-Encoding", "gzip"); - urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); + urlConnection = generatePostRequest(postURLPath, jsonPayload); + //If unauthorized, request new token - OutputStream os = urlConnection.getOutputStream(); - BufferedOutputStream writer = new BufferedOutputStream(os); - writer.write(gzipCompression.compress(jsonPayload)); - writer.flush(); - writer.close(); - os.close(); + if (HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode()) { + + String authToken = AccountHelper.getCachedOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + if (authToken != null) + AccountHelper.invalidateAuthToken(authenticatorXml.getAccountType(), authToken); - urlConnection.connect(); + urlConnection = generatePostRequest(postURLPath, jsonPayload); - return handleResponse(urlConnection); + } + + return processResponse(urlConnection); } catch (IOException ex) { Timber.e(ex, "EXCEPTION: %s", ex.toString()); @@ -193,6 +181,28 @@ public Response post(String postURLPath, String jsonPayload) { } } + @NonNull + private HttpURLConnection generatePostRequest(String postURLPath, String jsonPayload) throws IOException { + HttpURLConnection urlConnection; + urlConnection = initializeHttp(postURLPath, true); + + urlConnection.setDoOutput(true); + urlConnection.setRequestMethod("POST"); + urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8"); + urlConnection.setRequestProperty("Content-Encoding", "gzip"); + + + OutputStream os = urlConnection.getOutputStream(); + BufferedOutputStream writer = new BufferedOutputStream(os); + writer.write(gzipCompression.compress(jsonPayload)); + writer.flush(); + writer.close(); + os.close(); + + urlConnection.connect(); + return urlConnection; + } + public Response postWithJsonResponse(String postURLPath, String jsonPayload) { logResponse(postURLPath, jsonPayload); return post(postURLPath, jsonPayload); @@ -208,7 +218,7 @@ public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, Strin String url = null; try { url = requestURL.replaceAll("\\s+", ""); - urlConnection = initializeHttp(url); + urlConnection = initializeHttp(url, false); final String basicAuth = "Basic " + Base64.encodeToString((userName + ":" + password).getBytes(), Base64.NO_WRAP); urlConnection.setRequestProperty("Authorization", basicAuth); @@ -258,17 +268,29 @@ public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, Strin public DownloadStatus downloadFromUrl(String url, String filename) { AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); - Response status = downloadFromURL(url, filename, new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); + Response status = downloadFromURL(url, filename); Timber.d("downloading file name : %s and url %s", filename, url); return status.payload(); } - public Response fetchWithCredentials(String requestURL) { + public Response fetchWithCredentials(String requestURL, String accessToken) { try { - HttpURLConnection urlConnection = httpURLConnectionTries(requestURL); - return handleResponse(urlConnection); + HttpURLConnection urlConnection = initializeHttp(requestURL, false); + urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(accessToken).toString()); + + + //If unauthorized, request new token + if (HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode()) { + + AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); + AccountHelper.invalidateAuthToken(authenticatorXml.getAccountType(), accessToken); + + urlConnection = initializeHttp(requestURL, true); + + } + return processResponse(urlConnection); } catch (IOException ex) { Timber.e(ex, "EXCEPTION %s", ex.toString()); @@ -277,7 +299,7 @@ public Response fetchWithCredentials(String requestURL) { } - private Response handleResponse(HttpURLConnection urlConnection) { + private Response processResponse(HttpURLConnection urlConnection) { String responseString; String totalRecords; try { @@ -285,11 +307,7 @@ private Response handleResponse(HttpURLConnection urlConnection) { InputStream inputStream = null; - if (statusCode == HttpStatus.SC_UNAUTHORIZED) { - - syncUtils.logoutUser(); - - } else if (statusCode >= HttpStatus.SC_BAD_REQUEST) + if (statusCode >= HttpStatus.SC_BAD_REQUEST) inputStream = urlConnection.getErrorStream(); else inputStream = urlConnection.getInputStream(); @@ -330,18 +348,16 @@ public String httpImagePost(String urlString, ProfileImage image) { OutputStream outputStream; PrintWriter writer; String responseString = ""; - AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); HttpURLConnection httpUrlConnection = null; try { - httpUrlConnection = initializeHttp(urlString); + httpUrlConnection = initializeHttp(urlString, true); httpUrlConnection.setUseCaches(false); httpUrlConnection.setDoInput(true); httpUrlConnection.setDoOutput(true); httpUrlConnection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); - httpUrlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); outputStream = httpUrlConnection.getOutputStream(); @@ -532,7 +548,7 @@ public AccountResponse oauth2authenticate(String username, String password, Stri BufferedOutputStream writer = null; try { - urlConnection = initializeHttp(tokenEndpointURL); + urlConnection = initializeHttp(tokenEndpointURL, false); String clientId = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientId(); String clientSecret = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientSecret(); @@ -617,7 +633,8 @@ public LoginResponse fetchUserDetails(String requestURL, String oauthAccessToken try { url = requestURL.replaceAll("\\s+", ""); - urlConnection = httpURLConnectionTries(url); + urlConnection = initializeHttp(url, false); + urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(oauthAccessToken).toString()); int statusCode = urlConnection.getResponseCode(); @@ -666,10 +683,9 @@ public LoginResponse fetchUserDetails(String requestURL, String oauthAccessToken /** * @param downloadURL_ This is the url of the image * @param fileName This is how the image should be name after it has been downloaded. - * @param accessToken This is the access token used to authenticate when accessing the url endpoint. * @return Response This returns whether the download succeeded or failed. */ - public Response downloadFromURL(String downloadURL_, String fileName, String accessToken) { + public Response downloadFromURL(String downloadURL_, String fileName) { HttpURLConnection httpUrlConnection = null; try { @@ -689,19 +705,7 @@ public Response downloadFromURL(String downloadURL_, String file String downloadURL = downloadURL_.replaceAll("\\s+", ""); - /* Open connection to URL */ - URL url = new URL(downloadURL); - - httpUrlConnection = (HttpURLConnection) url.openConnection(); - - if (httpUrlConnection instanceof HttpsURLConnection) { - OpensrpSSLHelper opensrpSSLHelper = new OpensrpSSLHelper(context, configuration); - ((HttpsURLConnection) httpUrlConnection).setSSLSocketFactory(opensrpSSLHelper.getSSLSocketFactory()); - } - - httpUrlConnection.setRequestProperty("Authorization", accessToken); - - httpUrlConnection.connect(); + httpUrlConnection = initializeHttp(downloadURL, true); int status = httpUrlConnection.getResponseCode(); if (status == HttpURLConnection.HTTP_OK) { @@ -749,6 +753,7 @@ public Response downloadFromURL(String downloadURL_, String file } public boolean verifyAuthorization() { + String baseUrl = configuration.dristhiBaseURL(); if (baseUrl.endsWith("/")) { @@ -760,17 +765,23 @@ public boolean verifyAuthorization() { HttpURLConnection urlConnection = null; try { - URL url = new URL(baseUrl); - urlConnection = (HttpURLConnection) url.openConnection(); + + urlConnection = initializeHttp(baseUrl, true); AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); - if (AccountHelper.getOauthAccountByType(authenticatorXml.getAccountType()) != null) - urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); + int statusCode = urlConnection.getResponseCode(); - if (statusCode == HttpStatus.SC_UNAUTHORIZED) { + if (HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode()) { + + String authToken = AccountHelper.getCachedOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + if (authToken != null) + AccountHelper.invalidateAuthToken(authenticatorXml.getAccountType(), authToken); + + urlConnection = initializeHttp(baseUrl, true); + Timber.i("User not authorized. User access was revoked, will log off user"); - return false; + return HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode() ? false : true; } else if (statusCode != HttpStatus.SC_OK) { Timber.w("Error occurred verifying authorization, User will not be logged off"); } else { @@ -800,7 +811,7 @@ public AccountResponse oauth2authenticateRefreshToken(String refreshToken) { try { tokenEndpointURL = CoreLibrary.getInstance().context().allSharedPreferences().getPreferences().getString(AccountHelper.CONFIGURATION_CONSTANTS.TOKEN_ENDPOINT_URL, ""); - urlConnection = initializeHttp(tokenEndpointURL); + urlConnection = initializeHttp(tokenEndpointURL, false); final String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes()); @@ -872,22 +883,6 @@ public AccountResponse oauth2authenticateRefreshToken(String refreshToken) { } - private String refreshAuthenticationToken(String refreshToken) { - - AccountResponse response = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticateRefreshToken(refreshToken); - - AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); - AccountManager accountManager = CoreLibrary.getInstance().getAccountManager(); - - Account account = AccountHelper.getOauthAccountByType(authenticatorXml.getAccountType()); - - accountManager.setAuthToken(account, AccountHelper.TOKEN_TYPE.PROVIDER, response.getAccessToken()); - accountManager.setPassword(account, response.getAccessToken()); - accountManager.setUserData(account, AccountHelper.KEY_REFRESH_TOKEN, response.getRefreshToken()); - - return response.getAccessToken(); - } - public AccountConfiguration fetchOAuthConfiguration() { String baseUrl = configuration.dristhiBaseURL(); diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 117aa9738..01b199547 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -285,21 +285,6 @@ public boolean isUserInPioneerGroup(String userName) { return false; } - public LoginResponse oauth2Authenticate(String userName, String password) { - String requestURL; - - requestURL = configuration.dristhiBaseURL() + OPENSRP_AUTH_USER_URL_PATH; - - LoginResponse loginResponse = httpAgent - .urlCanBeAccessWithGivenCredentials(requestURL, userName, password); - - if (loginResponse != null && loginResponse.equals(LoginResponse.SUCCESS)) { - saveUserGroup(userName, password, loginResponse.payload()); - } - - return loginResponse; - } - public LoginResponse fetchUserDetails(String accessToken) { String requestURL; @@ -315,8 +300,7 @@ public LoginResponse isValidRemoteLogin(String userName, String password) { requestURL = configuration.dristhiBaseURL() + OPENSRP_AUTH_USER_URL_PATH; - LoginResponse loginResponse = httpAgent - .urlCanBeAccessWithGivenCredentials(requestURL, userName, password); + LoginResponse loginResponse = httpAgent.urlCanBeAccessWithGivenCredentials(requestURL, userName, password); if (loginResponse != null && loginResponse.equals(LoginResponse.SUCCESS)) { saveUserGroup(userName, password, loginResponse.payload()); @@ -334,23 +318,20 @@ public Response getLocationInformation() { return httpAgent.fetch(requestURL); } - private boolean loginWith(String userName, String password) { + private boolean loginWith(String userName) { boolean loginSuccessful = true; - if (usesGroupIdAsDBPassword(userName)) { - String encryptedGroupId = allSharedPreferences.fetchEncryptedGroupId(userName); - try { - KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); - if (privateKeyEntry != null) { - String groupId = decryptString(privateKeyEntry, encryptedGroupId); - setupContextForLogin(userName, groupId); - } - } catch (Exception e) { - Timber.e(e); - loginSuccessful = false; + String encryptedGroupId = allSharedPreferences.fetchEncryptedGroupId(userName); + try { + KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); + if (privateKeyEntry != null) { + String groupId = decryptString(privateKeyEntry, encryptedGroupId); + setupContextForLogin(userName, groupId); } - } else { - setupContextForLogin(userName, password); + } catch (Exception e) { + Timber.e(e); + loginSuccessful = false; } + String username = userName; if (allSharedPreferences.fetchRegisteredANM().equalsIgnoreCase(userName)) username = allSharedPreferences.fetchRegisteredANM(); @@ -379,14 +360,14 @@ private boolean usesGroupIdAsDBPassword(String userName) { return false; } - public void localLogin(String userName, String password) { - loginWith(userName, password); + public void localLogin(String userName) { + loginWith(userName); } - public void remoteLogin(String userName, String password, LoginResponseData userInfo) { + public void processLoginResponseDataForUser(String userName, LoginResponseData userInfo) { String username = userInfo.user != null && StringUtils.isNotBlank(userInfo.user.getUsername()) ? userInfo.user.getUsername() : userName; - boolean loginSuccessful = loginWith(username, password); + boolean loginSuccessful = loginWith(username); saveAnmLocation(getUserLocation(userInfo)); saveAnmTeam(getUserTeam(userInfo)); saveUserInfo(getUserData(userInfo)); diff --git a/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java b/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java index a3f5b51fd..0c52b8535 100644 --- a/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java @@ -8,6 +8,8 @@ import org.smartregister.AllConstants; import org.smartregister.CoreLibrary; import org.smartregister.SyncFilter; +import org.smartregister.account.AccountAuthenticatorXml; +import org.smartregister.account.AccountHelper; import org.smartregister.domain.Response; import org.smartregister.domain.Setting; import org.smartregister.domain.SyncStatus; @@ -28,6 +30,8 @@ public class SyncSettingsServiceHelper { private HTTPAgent httpAgent; private String baseUrl; + private String username; + private String password; private AllSharedPreferences sharedPreferences; public SyncSettingsServiceHelper(String baseUrl, HTTPAgent httpAgent) { @@ -65,7 +69,11 @@ public int processIntent() throws JSONException { } private JSONArray getSettings() throws JSONException { - JSONArray settings = pullSettingsFromServer(getInstance().getSyncConfiguration().getSettingsSyncFilterValue()); + + AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); + String authToken = AccountHelper.getCachedOAuthToken(sharedPreferences.fetchRegisteredANM(), authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + + JSONArray settings = pullSettingsFromServer(getInstance().getSyncConfiguration().getSettingsSyncFilterValue(), authToken); getGlobalSettings(settings); getExtraSettings(settings); return settings; @@ -101,12 +109,8 @@ private void getGlobalSettings(JSONArray settings) throws JSONException { globalSettings = pullGlobalSettingsFromServer(); } -<<<<<<< HEAD aggregateSettings(settings, globalSettings); } -======= - Response resp = httpAgent.fetchWithCredentials(url); ->>>>>>> Migrate user service private void aggregateSettings(JSONArray settings, JSONArray globalSettings) throws JSONException { if (!JsonFormUtils.isBlankJsonArray(globalSettings)) { @@ -150,9 +154,9 @@ private JSONObject pushSettingsToServer() throws JSONException { * @return settings {@link JSONArray} -- a JSON array of all the settings * @throws JSONException */ - public JSONArray pullSettingsFromServer(String syncFilterValue) throws JSONException { + public JSONArray pullSettingsFromServer(String syncFilterValue, String accessToken) throws JSONException { String url = SettingsSyncIntentService.SETTINGS_URL + "?" + getSettingsSyncFilterParam().value() + "=" + syncFilterValue + "&" + AllConstants.SERVER_VERSION + "=" + sharedPreferences.fetchLastSettingsSyncTimeStamp(); - return pullSettings(url); + return pullSettings(url, accessToken); } @VisibleForTesting @@ -186,7 +190,7 @@ private JSONObject createSettingsConfigurationPayload() throws JSONException { return siteSettingsPayload; } - private JSONArray pullSettings(String directoryUrl) throws JSONException { + private JSONArray pullSettings(String directoryUrl, String accessToken) throws JSONException { String endString = "/"; if (baseUrl.endsWith(endString)) { baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf(endString)); @@ -201,7 +205,7 @@ private JSONArray pullSettings(String directoryUrl) throws JSONException { return null; } - Response resp = getResponse(completeUrl); + Response resp = getResponse(completeUrl, accessToken); if (resp == null || resp.isFailure()) { Timber.e(" %s not returned data ", completeUrl); @@ -212,8 +216,8 @@ private JSONArray pullSettings(String directoryUrl) throws JSONException { } @VisibleForTesting - protected Response getResponse(String completeUrl) { - return httpAgent.fetchWithCredentials(completeUrl, getUsername(), getPassword()); + protected Response getResponse(String completeUrl, String accessToken) { + return httpAgent.fetchWithCredentials(completeUrl, accessToken); } public String getUsername() { @@ -231,6 +235,4 @@ public String getPassword() { public void setPassword(String password) { this.password = password; } - } - diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java index d548dc714..2a6e31d0b 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java @@ -109,13 +109,10 @@ protected void attachBaseContext(android.content.Context base) { } public Repository getRepository() { - ArrayList drishtireposotorylist = CoreLibrary.getInstance().context() - .sharedRepositories(); - DrishtiRepository[] drishtireposotoryarray = drishtireposotorylist - .toArray(new DrishtiRepository[drishtireposotorylist.size()]); + ArrayList drishtiRepositoryList = CoreLibrary.getInstance().context().sharedRepositories(); + DrishtiRepository[] drishtiRepositoryArray = drishtiRepositoryList.toArray(new DrishtiRepository[drishtiRepositoryList.size()]); if (repository == null) { - repository = new Repository(getInstance().getApplicationContext(), null, - drishtireposotoryarray); + repository = new Repository(getInstance().getApplicationContext(), null, drishtiRepositoryArray); } return repository; } diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java index a18656057..26a389cb7 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java @@ -160,7 +160,7 @@ private void localLogin(View view, String userName, String password) { } if (context.userService().isValidLocalLogin(userName, password)) { - localLoginWith(userName, password); + localLoginWith(userName); } else { showErrorDialog(getString(R.string.login_failed_dialog_message)); view.setClickable(true); @@ -184,7 +184,7 @@ private void remoteLogin(final View view, final String userName, final String pa tryRemoteLogin(userName, password, new Listener() { public void onEvent(LoginResponse loginResponse) { if (loginResponse == SUCCESS) { - remoteLoginWith(userName, password, loginResponse.payload()); + remoteLoginWith(userName, loginResponse.payload()); } else { if (loginResponse == null) { showErrorDialog("Login failed. Unknown reason. Try Again"); @@ -254,14 +254,14 @@ private void hideKeyboard() { inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), HIDE_NOT_ALWAYS); } - private void localLoginWith(String userName, String password) { - context.userService().localLogin(userName, password); + private void localLoginWith(String userName) { + context.userService().localLogin(userName); goToHome(); DrishtiSyncScheduler.startOnlyIfConnectedToNetwork(getApplicationContext()); } - private void remoteLoginWith(String userName, String password, LoginResponseData userInfo) { - context.userService().remoteLogin(userName, password, userInfo); + private void remoteLoginWith(String userName, LoginResponseData userInfo) { + context.userService().processLoginResponseDataForUser(userName, userInfo); goToHome(); DrishtiSyncScheduler.startOnlyIfConnectedToNetwork(getApplicationContext()); } diff --git a/opensrp-app/src/test/java/org/smartregister/TestSyncConfiguration.java b/opensrp-app/src/test/java/org/smartregister/TestSyncConfiguration.java index 675cbc9ff..d018f9a55 100644 --- a/opensrp-app/src/test/java/org/smartregister/TestSyncConfiguration.java +++ b/opensrp-app/src/test/java/org/smartregister/TestSyncConfiguration.java @@ -1,5 +1,7 @@ package org.smartregister; +import org.smartregister.view.activity.BaseLoginActivity; + import java.util.List; /** @@ -65,4 +67,9 @@ public String getOauthClientId() { public String getOauthClientSecret() { return "$om3cl13nt$3cret"; } + + @Override + public Class getAuthenticationActivity() { + return BaseLoginActivity.class; + } } diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index c54f82a8f..f97d7c222 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -155,14 +155,14 @@ public void testUrlCanBeAccessWithGivenCredentialsGivenEmptyResp() { @Test public void testfetchWithCredentialsFailsGivenWrongUrl() { - Response resp = httpAgent.fetchWithCredentials("wrong.url"); + Response resp = httpAgent.fetchWithCredentials("wrong.url","sample-test-token"); Assert.assertEquals(ResponseStatus.failure, resp.status()); } @Test public void testfetchWithCredentialsPassesGivenCorrectUrl() { PowerMockito.mockStatic(Base64.class); - Response resp = httpAgent.fetchWithCredentials("https://google.com"); + Response resp = httpAgent.fetchWithCredentials("https://google.com","sample-test-token"); Assert.assertEquals(ResponseStatus.success, resp.status()); } diff --git a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java index 315fdf46b..0830efa34 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java @@ -209,10 +209,9 @@ public void logoutCurrentUser() { when(allSharedPreferences.fetchRegisteredANM()).thenReturn("user X"); - userService.remoteLogin("user X", "password Y", userInfo); + userService.processLoginResponseDataForUser("user X", userInfo); verify(allSettings).registerANM("user X"); - verify(session).setPassword("password Y"); } @Test diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityTest.java index 0c8edd581..8c20a2818 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityTest.java @@ -121,7 +121,7 @@ public void localLoginTest() { Button login_button = activity.findViewById(R.id.login_loginButton); login_button.performClick(); - Mockito.verify(userService, Mockito.atLeastOnce()).localLogin(anyString(), anyString()); + Mockito.verify(userService, Mockito.atLeastOnce()).localLogin(anyString()); } diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityWithRemoteLoginTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityWithRemoteLoginTest.java index f06c432cb..754739d50 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityWithRemoteLoginTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityWithRemoteLoginTest.java @@ -136,7 +136,7 @@ public void remoteLoginTest() { password.setText("password"); Button login_button = activity.findViewById(R.id.login_loginButton); login_button.performClick(); - Mockito.verify(userService, Mockito.atLeastOnce()).remoteLogin(anyString(), anyString(), any(LoginResponseData.class)); + Mockito.verify(userService, Mockito.atLeastOnce()).processLoginResponseDataForUser(anyString(), any(LoginResponseData.class)); destroyController(); diff --git a/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java b/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java index 61eff77ad..2c2fac785 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java @@ -18,6 +18,7 @@ import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; @@ -116,6 +117,9 @@ public void setUp() { intent.putExtra(BaseRegisterFragment.TOOLBAR_TITLE, TEST_RANDOM_STRING); activity = Robolectric.buildActivity(AppCompatActivity.class, intent).get(); + + AppCompatActivity activitySpy = Mockito.spy(activity); + Mockito.doReturn(activitySpy).when(baseRegisterFragment).getActivity(); } @Test From 428792a34c0d13d3d7b8c0867956edae0703298e Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Fri, 29 May 2020 04:42:56 +0300 Subject: [PATCH 14/70] Fix profile image loading Add add http request header param enums --- .../java/org/smartregister/AllConstants.java | 8 +++++++ .../account/AccountResponse.java | 4 ++++ .../smartregister/repository/AllSettings.java | 8 ------- .../org/smartregister/service/HTTPAgent.java | 23 ++++++++++--------- .../service/ImageUploadSyncService.java | 4 ++++ .../sync/CloudantSyncHandler.java | 8 +++---- .../util/OpenSRPImageLoader.java | 22 +++++++++--------- .../repository/AllSettingsTest.java | 8 ------- 8 files changed, 43 insertions(+), 42 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/AllConstants.java b/opensrp-app/src/main/java/org/smartregister/AllConstants.java index 038c8a088..e0f4339b1 100644 --- a/opensrp-app/src/main/java/org/smartregister/AllConstants.java +++ b/opensrp-app/src/main/java/org/smartregister/AllConstants.java @@ -498,4 +498,12 @@ public interface P2PDataTypes { String FOREIGN_CLIENT = "ForeignClient"; String FOREIGN_EVENT = "ForeignEvent"; } + public static class HTTP_REQUEST_HEADERS { + public static String AUTHORIZATION = "Authorization"; + } + + public static class HTTP_REQUEST_AUTH_TOKEN_TYPE { + public static String BEARER = "Bearer"; + public static String BASIC = "Basic"; + } } diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java b/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java index 9c7759cb9..1094df6c6 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java @@ -38,6 +38,10 @@ public int getStatus() { return status; } + public void setStatus(int status) { + this.status = status; + } + public AccountError getAccountError() { return accountError; } diff --git a/opensrp-app/src/main/java/org/smartregister/repository/AllSettings.java b/opensrp-app/src/main/java/org/smartregister/repository/AllSettings.java index b063c8819..df1c1e4ff 100644 --- a/opensrp-app/src/main/java/org/smartregister/repository/AllSettings.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/AllSettings.java @@ -2,9 +2,7 @@ import org.smartregister.domain.Setting; -import java.util.HashMap; import java.util.List; -import java.util.Map; public class AllSettings { public static final String APPLIED_VILLAGE_FILTER_SETTING_KEY = "appliedVillageFilter"; @@ -71,12 +69,6 @@ public String fetchUserInformation() { return settingsRepository.querySetting(USER_INFORMATION, ""); } - public Map getAuthParams() { - Map authParams = new HashMap(); - authParams.put("username", preferences.fetchRegisteredANM()); - return authParams; - } - public void put(String key, String value) { settingsRepository.updateSetting(key, value); } diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index 9c8a2bdb8..d39360891 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -123,7 +123,7 @@ private HttpURLConnection initializeHttp(String requestURLPath, boolean setOauth if (setOauthToken) { AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); if (AccountHelper.getOauthAccountByType(authenticatorXml.getAccountType()) != null) - urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); + urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, new StringBuilder(AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); } return urlConnection; } @@ -220,8 +220,8 @@ public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, Strin url = requestURL.replaceAll("\\s+", ""); urlConnection = initializeHttp(url, false); - final String basicAuth = "Basic " + Base64.encodeToString((userName + ":" + password).getBytes(), Base64.NO_WRAP); - urlConnection.setRequestProperty("Authorization", basicAuth); + final String basicAuth = AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + Base64.encodeToString((userName + ":" + password).getBytes(), Base64.NO_WRAP); + urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, basicAuth); int statusCode = urlConnection.getResponseCode(); InputStream inputStream; if (statusCode >= HttpStatus.SC_BAD_REQUEST) @@ -267,7 +267,6 @@ public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, Strin public DownloadStatus downloadFromUrl(String url, String filename) { - AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); Response status = downloadFromURL(url, filename); Timber.d("downloading file name : %s and url %s", filename, url); return status.payload(); @@ -278,7 +277,7 @@ public Response fetchWithCredentials(String requestURL, String accessTok try { HttpURLConnection urlConnection = initializeHttp(requestURL, false); - urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(accessToken).toString()); + urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, new StringBuilder(AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " ").append(accessToken).toString()); //If unauthorized, request new token @@ -390,10 +389,10 @@ public String httpImagePost(String urlString, ProfileImage image) { } reader.close(); } else { - Timber.d("SERVER RESPONSE %s Server returned non-OK status: %s :-", status, httpUrlConnection.getResponseMessage()); + Timber.e("SERVER RESPONSE %s Server returned non-OK status: %s :-", status, httpUrlConnection.getResponseMessage()); BufferedReader reader = new BufferedReader(new InputStreamReader(httpUrlConnection.getErrorStream())); while ((line = reader.readLine()) != null) { - Timber.d("SERVER RESPONSE %s", line); + Timber.e("SERVER RESPONSE %s", line); } reader.close(); } @@ -571,7 +570,7 @@ public AccountResponse oauth2authenticate(String username, String password, Stri urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); urlConnection.setRequestProperty("charset", "utf-8"); urlConnection.setRequestProperty("Content-Length", Integer.toString(postDataLength)); - urlConnection.setRequestProperty("Authorization", "Basic " + base64Auth); + urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + base64Auth); urlConnection.setUseCaches(false); outputStream = urlConnection.getOutputStream(); @@ -591,6 +590,7 @@ public AccountResponse oauth2authenticate(String username, String password, Stri Timber.d("response String: %s using request url %s", responseString, url); AccountResponse accountResponse = gson.fromJson(responseString, AccountResponse.class); + accountResponse.setStatus(statusCode); return accountResponse; } else { @@ -634,7 +634,7 @@ public LoginResponse fetchUserDetails(String requestURL, String oauthAccessToken url = requestURL.replaceAll("\\s+", ""); urlConnection = initializeHttp(url, false); - urlConnection.setRequestProperty("Authorization", new StringBuilder("Bearer ").append(oauthAccessToken).toString()); + urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, new StringBuilder(AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " ").append(oauthAccessToken).toString()); int statusCode = urlConnection.getResponseCode(); @@ -830,11 +830,11 @@ public AccountResponse oauth2authenticateRefreshToken(String refreshToken) { urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); urlConnection.setRequestProperty("charset", "utf-8"); urlConnection.setRequestProperty("Content-Length", Integer.toString(postDataLength)); - urlConnection.setRequestProperty("Authorization", "Basic " + base64Auth); + urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + base64Auth); urlConnection.setUseCaches(false); outputStream = urlConnection.getOutputStream(); - writer = new BufferedOutputStream(outputStream);//Bearer + writer = new BufferedOutputStream(outputStream); writer.write(postData); writer.flush(); @@ -849,6 +849,7 @@ public AccountResponse oauth2authenticateRefreshToken(String refreshToken) { Timber.d("response String: %s using request url %s", responseString, tokenEndpointURL); AccountResponse accountResponse = gson.fromJson(responseString, AccountResponse.class); + accountResponse.setStatus(statusCode); return accountResponse; } else { diff --git a/opensrp-app/src/main/java/org/smartregister/service/ImageUploadSyncService.java b/opensrp-app/src/main/java/org/smartregister/service/ImageUploadSyncService.java index 21d92aa1a..691146844 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/ImageUploadSyncService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/ImageUploadSyncService.java @@ -10,6 +10,8 @@ import java.util.List; +import timber.log.Timber; + import static org.smartregister.util.Log.logError; import static org.smartregister.util.Log.logInfo; @@ -40,6 +42,8 @@ protected void onHandleIntent(Intent intent) { + AllConstants.PROFILE_IMAGES_UPLOAD_PATH, profileImages.get(i)); if (response.contains("success")) { imageRepo.close(profileImages.get(i).getImageid()); + } else { + Timber.e(new StringBuilder("Image Upload: could NOT upload image ID: ").append(profileImages.get(i).getImageid()).append(" PATH: ").append(profileImages.get(i).getFilepath()).toString()); } } } catch (Exception e) { diff --git a/opensrp-app/src/main/java/org/smartregister/sync/CloudantSyncHandler.java b/opensrp-app/src/main/java/org/smartregister/sync/CloudantSyncHandler.java index 099cf9c47..94a0047a5 100644 --- a/opensrp-app/src/main/java/org/smartregister/sync/CloudantSyncHandler.java +++ b/opensrp-app/src/main/java/org/smartregister/sync/CloudantSyncHandler.java @@ -358,8 +358,8 @@ public Boolean setReplicationFilter(JsonObject jsonObject, String designDocument String authEncoded = getAuthorization(); if (authEncoded != null) { - String basicAuth = "Basic " + authEncoded; - conn.setRequestProperty("Authorization", basicAuth); + String basicAuth = AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + authEncoded; + conn.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, basicAuth); } OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); @@ -392,8 +392,8 @@ public JsonObject getReplicationFiler(String designDocumentId) { String authEncoded = getAuthorization(); if (authEncoded != null) { - String basicAuth = "Basic " + authEncoded; - get.setHeader("Authorization", basicAuth); + String basicAuth = AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + authEncoded; + get.setHeader(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, basicAuth); } HttpResponse response = httpclient.execute(get); diff --git a/opensrp-app/src/main/java/org/smartregister/util/OpenSRPImageLoader.java b/opensrp-app/src/main/java/org/smartregister/util/OpenSRPImageLoader.java index 771d28dea..2ebede8e3 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/OpenSRPImageLoader.java +++ b/opensrp-app/src/main/java/org/smartregister/util/OpenSRPImageLoader.java @@ -34,6 +34,7 @@ import org.smartregister.AllConstants; import org.smartregister.CoreLibrary; import org.smartregister.R; +import org.smartregister.account.AccountHelper; import org.smartregister.domain.ProfileImage; import org.smartregister.repository.ImageRepository; import org.smartregister.view.activity.DrishtiApplication; @@ -159,8 +160,8 @@ private static RequestQueue newRequestQueue(Context context) { public HttpResponse performRequest(Request request, Map headers) throws IOException, AuthFailureError { - headers.putAll( - CoreLibrary.getInstance().context().allSettings().getAuthParams()); + String accessToken = AccountHelper.getOAuthToken(CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + headers.put(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, new StringBuilder(AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " ").append(accessToken).toString()); return super.performRequest(request, headers); } @@ -172,11 +173,10 @@ public HttpResponse performRequest(Request request, Map HttpClientStack stack = new HttpClientStack( AndroidHttpClient.newInstance(FileUtilities.getUserAgent(context))) { @Override - public HttpResponse performRequest(Request request, Map - headers) throws IOException, AuthFailureError { + public HttpResponse performRequest(Request request, Map headers) throws IOException, AuthFailureError { - headers.putAll(CoreLibrary.getInstance().context().allSettings(). - getAuthParams()); + String accessToken = AccountHelper.getOAuthToken(CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + headers.put(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, new StringBuilder(AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " ").append(accessToken).toString()); return super.performRequest(request, headers); } @@ -386,13 +386,13 @@ public static void saveStaticImageToDisk(String entityId, Bitmap image) { } } catch (FileNotFoundException e) { - Timber.e( "Failed to save static image to disk"); + Timber.e("Failed to save static image to disk"); } finally { if (os != null) { try { os.close(); } catch (IOException e) { - Timber.e( "Failed to close static images output stream after attempting" + Timber.e("Failed to close static images output stream after attempting" + " to write image"); } } @@ -515,7 +515,7 @@ public void getImageByClientId(String entityId, OpenSRPImageListener opensrpImag } } catch (Exception e) { - Timber.e( e.getMessage(), e); + Timber.e(e.getMessage(), e); } } @@ -537,7 +537,7 @@ public void get(final ProfileImage image, final OpenSRPImageListener opensrpImag startAsyncTask(loadBitmap, filePathArray); } catch (Exception e) { - Timber.e( e.getMessage(), e); + Timber.e(e.getMessage(), e); } } @@ -701,7 +701,7 @@ protected void onPostExecute(Bitmap result) { } catch (Exception exc) { - Timber.e( exc.getMessage(), exc); + Timber.e(exc.getMessage(), exc); } diff --git a/opensrp-app/src/test/java/org/smartregister/repository/AllSettingsTest.java b/opensrp-app/src/test/java/org/smartregister/repository/AllSettingsTest.java index c3bca5f8a..9aab8c39d 100644 --- a/opensrp-app/src/test/java/org/smartregister/repository/AllSettingsTest.java +++ b/opensrp-app/src/test/java/org/smartregister/repository/AllSettingsTest.java @@ -11,7 +11,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -117,13 +116,6 @@ public void assertSaveUserInformationCallsRepositoryUpdate() { Mockito.verify(settingsRepository, Mockito.times(1)).updateSetting(Mockito.anyString(), Mockito.anyString()); } - @Test - public void assertGetAuthParamsReturnsUserNamePassword() { - Mockito.when(allSharedPreferences.fetchRegisteredANM()).thenReturn("username"); - Map auth = allSettings.getAuthParams(); - Assert.assertEquals("username", auth.get("username")); - } - @Test public void testGetWithDefaultShouldReturnCorrectValue() { SettingsRepository settingsRepository = spy(new SettingsRepository()); From ba23de2dd455fa9716bb1fa6445558f94f09c0f6 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 2 Jun 2020 08:56:17 +0300 Subject: [PATCH 15/70] Optimization, Refactor and unit testing - Optimize upstream data processing for file uploads - Refactor for maintainability - Unit testing --- .../smartregister/account/AccountError.java | 9 + .../account/AccountResponse.java | 4 +- .../org/smartregister/service/HTTPAgent.java | 194 +++------ .../view/fragment/BaseRegisterFragment.java | 18 +- .../smartregister/service/HTTPAgentTest.java | 378 +++++++++++++++++- .../fragment/BaseRegisterFragmentTest.java | 28 +- 6 files changed, 481 insertions(+), 150 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountError.java b/opensrp-app/src/main/java/org/smartregister/account/AccountError.java index 6b972cbfa..36057a684 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountError.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountError.java @@ -1,5 +1,7 @@ package org.smartregister.account; +import com.google.gson.annotations.SerializedName; + import java.io.Serializable; /** @@ -7,8 +9,11 @@ */ public class AccountError implements Serializable { + @SerializedName("status_code") private int statusCode; private String error; + @SerializedName("error_description") + private String errorDescription; public AccountError(int statusCode, String error) { this.statusCode = statusCode; @@ -23,4 +28,8 @@ public int getStatusCode() { public String getError() { return error; } + + public String getErrorDescription() { + return errorDescription; + } } diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java b/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java index 1094df6c6..fd810d686 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountResponse.java @@ -23,7 +23,7 @@ public class AccountResponse { private Integer expiresIn; @SerializedName("scope") - private String Scope; + private String scope; private int status; @@ -63,7 +63,7 @@ public Integer getExpiresIn() { } public String getScope() { - return Scope; + return scope; } public Integer getRefreshExpiresIn() { diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index d39360891..725240ae0 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -2,6 +2,7 @@ import android.content.Context; import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; import android.util.Base64; import android.util.Log; @@ -31,7 +32,6 @@ import org.smartregister.domain.jsonmapping.LoginResponseData; import org.smartregister.repository.AllSharedPreferences; import org.smartregister.ssl.OpensrpSSLHelper; -import org.smartregister.util.SyncUtils; import org.smartregister.util.Utils; import java.io.BufferedInputStream; @@ -81,6 +81,9 @@ import static org.smartregister.util.HttpResponseUtil.getResponseBody; public class HTTPAgent { + + private static final int FILE_UPLOAD_CHUNK_SIZE_BYTES = 4096; + private Context context; private AllSharedPreferences allSharedPreferences; private DristhiConfiguration configuration; @@ -96,7 +99,6 @@ public class HTTPAgent { private static final String DETAILS_URL = "/user-details?anm-id="; - private SyncUtils syncUtils; public HTTPAgent(Context context, AllSharedPreferences allSharedPreferences, DristhiConfiguration configuration) { @@ -105,15 +107,15 @@ public HTTPAgent(Context context, AllSharedPreferences this.configuration = configuration; gson = new Gson(); gzipCompression = new GZIPCompression(); - syncUtils = new SyncUtils(context.getApplicationContext()); } /** * This method initializes httpurlconnection */ private HttpURLConnection initializeHttp(String requestURLPath, boolean setOauthToken) throws IOException { - URL url = new URL(requestURLPath); - HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + + HttpURLConnection urlConnection = getHttpURLConnection(requestURLPath); + if (urlConnection instanceof HttpsURLConnection) { OpensrpSSLHelper opensrpSSLHelper = new OpensrpSSLHelper(context, configuration); ((HttpsURLConnection) urlConnection).setSSLSocketFactory(opensrpSSLHelper.getSSLSocketFactory()); @@ -128,19 +130,21 @@ private HttpURLConnection initializeHttp(String requestURLPath, boolean setOauth return urlConnection; } + @VisibleForTesting + protected HttpURLConnection getHttpURLConnection(String requestURLPath) throws IOException { + URL url = new URL(requestURLPath); + return (HttpURLConnection) url.openConnection(); + } + public Response fetch(String requestURLPath) { try { HttpURLConnection urlConnection = initializeHttp(requestURLPath, true); - //If unauthorized, request new token + //If unauthorized invalidate cache of old token retry if (HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode()) { - AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); - - String authToken = AccountHelper.getCachedOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); - if (authToken != null) - AccountHelper.invalidateAuthToken(authenticatorXml.getAccountType(), authToken); + invalidateExpiredCachedAccessToken(); urlConnection = initializeHttp(requestURLPath, true); @@ -154,20 +158,23 @@ public Response fetch(String requestURLPath) { } } + public void invalidateExpiredCachedAccessToken() { + AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); + String authToken = AccountHelper.getCachedOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + if (authToken != null) + AccountHelper.invalidateAuthToken(authenticatorXml.getAccountType(), authToken); + } + public Response post(String postURLPath, String jsonPayload) { HttpURLConnection urlConnection; - AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); try { urlConnection = generatePostRequest(postURLPath, jsonPayload); - //If unauthorized, request new token - + //If unauthorized invalidate cache of old token retry if (HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode()) { - String authToken = AccountHelper.getCachedOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); - if (authToken != null) - AccountHelper.invalidateAuthToken(authenticatorXml.getAccountType(), authToken); + invalidateExpiredCachedAccessToken(); urlConnection = generatePostRequest(postURLPath, jsonPayload); @@ -182,7 +189,8 @@ public Response post(String postURLPath, String jsonPayload) { } @NonNull - private HttpURLConnection generatePostRequest(String postURLPath, String jsonPayload) throws IOException { + @VisibleForTesting + protected HttpURLConnection generatePostRequest(String postURLPath, String jsonPayload) throws IOException { HttpURLConnection urlConnection; urlConnection = initializeHttp(postURLPath, true); @@ -191,10 +199,13 @@ private HttpURLConnection generatePostRequest(String postURLPath, String jsonPay urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8"); urlConnection.setRequestProperty("Content-Encoding", "gzip"); + byte[] content = gzipCompression.compress(jsonPayload); + urlConnection.setFixedLengthStreamingMode(content.length); OutputStream os = urlConnection.getOutputStream(); BufferedOutputStream writer = new BufferedOutputStream(os); - writer.write(gzipCompression.compress(jsonPayload)); + + writer.write(content); writer.flush(); writer.close(); os.close(); @@ -279,8 +290,7 @@ public Response fetchWithCredentials(String requestURL, String accessTok HttpURLConnection urlConnection = initializeHttp(requestURL, false); urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, new StringBuilder(AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " ").append(accessToken).toString()); - - //If unauthorized, request new token + //If unauthorized invalidate cache of old token retry if (HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode()) { AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); @@ -353,11 +363,12 @@ public String httpImagePost(String urlString, ProfileImage image) { httpUrlConnection = initializeHttp(urlString, true); + httpUrlConnection.setRequestMethod("POST"); httpUrlConnection.setUseCaches(false); httpUrlConnection.setDoInput(true); httpUrlConnection.setDoOutput(true); httpUrlConnection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); - + httpUrlConnection.setChunkedStreamingMode(FILE_UPLOAD_CHUNK_SIZE_BYTES); outputStream = httpUrlConnection.getOutputStream(); writer = new PrintWriter(new OutputStreamWriter(outputStream, "UTF-8"), true); @@ -424,7 +435,7 @@ private void attachImage(PrintWriter writer, ProfileImage image, OutputStream ou writer.flush(); FileInputStream inputStream = new FileInputStream(uploadImageFile); - byte[] buffer = new byte[4096]; + byte[] buffer = new byte[FILE_UPLOAD_CHUNK_SIZE_BYTES]; int bytesRead = -1; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); @@ -538,13 +549,14 @@ public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } - public AccountResponse oauth2authenticate(String username, String password, String grantType, String tokenEndpointURL) { + public AccountResponse oauth2authenticateCore(StringBuilder requestParamBuilder, String grantType, String tokenEndpointURL) { - AccountError accountError = null; + + AccountError accountError; HttpURLConnection urlConnection = null; - String url = null; OutputStream outputStream = null; BufferedOutputStream writer = null; + InputStream inputStream; try { urlConnection = initializeHttp(tokenEndpointURL, false); @@ -554,16 +566,14 @@ public AccountResponse oauth2authenticate(String username, String password, Stri final String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes()); - StringBuilder requestParamBuilder = new StringBuilder(); requestParamBuilder.append("&grant_type=").append(grantType); - requestParamBuilder.append("&username=").append(username); - requestParamBuilder.append("&password=").append(password); requestParamBuilder.append("&client_id=").append(clientId); requestParamBuilder.append("&client_secret=").append(clientSecret); byte[] postData = requestParamBuilder.toString().getBytes(CharEncoding.UTF_8); int postDataLength = postData.length; + urlConnection.setFixedLengthStreamingMode(postDataLength); urlConnection.setDoOutput(true); urlConnection.setInstanceFollowRedirects(false); urlConnection.setRequestMethod("POST"); @@ -579,7 +589,6 @@ public AccountResponse oauth2authenticate(String username, String password, Stri writer.flush(); int statusCode = urlConnection.getResponseCode(); - InputStream inputStream; if (statusCode >= HttpStatus.SC_BAD_REQUEST) inputStream = urlConnection.getErrorStream(); else @@ -587,7 +596,7 @@ public AccountResponse oauth2authenticate(String username, String password, Stri String responseString = IOUtils.toString(inputStream); if (statusCode == HttpStatus.SC_OK) { - Timber.d("response String: %s using request url %s", responseString, url); + Timber.d("response String: %s using request url %s", responseString, tokenEndpointURL); AccountResponse accountResponse = gson.fromJson(responseString, AccountResponse.class); accountResponse.setStatus(statusCode); @@ -600,17 +609,17 @@ public AccountResponse oauth2authenticate(String username, String password, Stri } } catch (MalformedURLException e) { - Timber.e(e, "Failed to check credentials bad url %s", url); + Timber.e(e, "Failed to check credentials bad url %s", tokenEndpointURL); accountError = new AccountError(0, MALFORMED_URL.name()); } catch (SocketTimeoutException e) { - Timber.e(e, "SocketTimeoutException when authenticating %s", username); + Timber.e(e, "SocketTimeoutException when authenticating"); accountError = new AccountError(0, TIMEOUT.name()); - Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", username, url, e.toString()); + Timber.e(e, "Failed to check credentials using %s . Error: %s", tokenEndpointURL, e.toString()); } catch (IOException e) { - Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", username, url, e.toString()); + Timber.e(e, "Failed to check credentials using %s . Error: %s", tokenEndpointURL, e.toString()); accountError = new AccountError(0, NO_INTERNET_CONNECTIVITY.name()); } finally { @@ -626,6 +635,24 @@ public AccountResponse oauth2authenticate(String username, String password, Stri } + public AccountResponse oauth2authenticate(String username, String password, String grantType, String tokenEndpointURL) { + + StringBuilder requestParamBuilder = new StringBuilder(); + requestParamBuilder.append("&username=").append(username); + requestParamBuilder.append("&password=").append(password); + + return oauth2authenticateCore(requestParamBuilder, grantType, tokenEndpointURL); + } + + public AccountResponse oauth2authenticateRefreshToken(String refreshToken) { + + String tokenEndpointURL = allSharedPreferences.getPreferences().getString(AccountHelper.CONFIGURATION_CONSTANTS.TOKEN_ENDPOINT_URL, ""); + StringBuilder requestParamBuilder = new StringBuilder(); + requestParamBuilder.append("&refresh_token=").append(refreshToken); + + return oauth2authenticateCore(requestParamBuilder, AccountHelper.OAUTH.GRANT_TYPE.REFRESH_TOKEN, tokenEndpointURL); + } + public LoginResponse fetchUserDetails(String requestURL, String oauthAccessToken) { LoginResponse loginResponse = null; String url = null; @@ -768,15 +795,11 @@ public boolean verifyAuthorization() { urlConnection = initializeHttp(baseUrl, true); - AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); - int statusCode = urlConnection.getResponseCode(); if (HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode()) { - String authToken = AccountHelper.getCachedOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); - if (authToken != null) - AccountHelper.invalidateAuthToken(authenticatorXml.getAccountType(), authToken); + invalidateExpiredCachedAccessToken(); urlConnection = initializeHttp(baseUrl, true); @@ -797,93 +820,6 @@ public boolean verifyAuthorization() { return true; } - public AccountResponse oauth2authenticateRefreshToken(String refreshToken) { - - AccountError accountError = null; - HttpURLConnection urlConnection = null; - OutputStream outputStream = null; - BufferedOutputStream writer = null; - InputStream inputStream = null; - - String clientId = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientId(); - String clientSecret = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientSecret(); - String tokenEndpointURL = null; - try { - - tokenEndpointURL = CoreLibrary.getInstance().context().allSharedPreferences().getPreferences().getString(AccountHelper.CONFIGURATION_CONSTANTS.TOKEN_ENDPOINT_URL, ""); - urlConnection = initializeHttp(tokenEndpointURL, false); - - final String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes()); - - StringBuilder requestParamBuilder = new StringBuilder(); - requestParamBuilder.append("&grant_type=").append(AccountHelper.OAUTH.GRANT_TYPE.REFRESH_TOKEN); - requestParamBuilder.append("&refresh_token=").append(refreshToken); - requestParamBuilder.append("&client_id=").append(clientId); - requestParamBuilder.append("&client_secret=").append(clientSecret); - - byte[] postData = requestParamBuilder.toString().getBytes(CharEncoding.UTF_8); - int postDataLength = postData.length; - - urlConnection.setDoOutput(true); - urlConnection.setInstanceFollowRedirects(false); - urlConnection.setRequestMethod("POST"); - urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - urlConnection.setRequestProperty("charset", "utf-8"); - urlConnection.setRequestProperty("Content-Length", Integer.toString(postDataLength)); - urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + base64Auth); - urlConnection.setUseCaches(false); - - outputStream = urlConnection.getOutputStream(); - writer = new BufferedOutputStream(outputStream); - writer.write(postData); - writer.flush(); - - int statusCode = urlConnection.getResponseCode(); - if (statusCode >= HttpStatus.SC_BAD_REQUEST) - inputStream = urlConnection.getErrorStream(); - else - inputStream = urlConnection.getInputStream(); - String responseString = IOUtils.toString(inputStream); - if (statusCode == HttpStatus.SC_OK) { - - Timber.d("response String: %s using request url %s", responseString, tokenEndpointURL); - - AccountResponse accountResponse = gson.fromJson(responseString, AccountResponse.class); - accountResponse.setStatus(statusCode); - return accountResponse; - - } else { - - accountError = gson.fromJson(responseString, AccountError.class); - return new AccountResponse(statusCode, accountError); - - } - } catch (MalformedURLException e) { - Timber.e(e, "Failed to check credentials bad url %s", tokenEndpointURL); - accountError = new AccountError(0, MALFORMED_URL.name()); - - } catch (SocketTimeoutException e) { - Timber.e(e, "SocketTimeoutException when authenticating %s", refreshToken); - - accountError = new AccountError(0, TIMEOUT.name()); - - Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", refreshToken, tokenEndpointURL, e.toString()); - } catch (IOException e) { - Timber.e(e, "Failed to check credentials of: %s using %s . Error: %s", refreshToken, tokenEndpointURL, e.toString()); - accountError = new AccountError(0, NO_INTERNET_CONNECTIVITY.name()); - - } finally { - closeConnection(urlConnection); - closeIOStream(writer); - closeIOStream(outputStream); - - } - - //If we got here there was an issue with no server status code - return new AccountResponse(0, accountError); - - } - public AccountConfiguration fetchOAuthConfiguration() { String baseUrl = configuration.dristhiBaseURL(); @@ -898,8 +834,8 @@ public AccountConfiguration fetchOAuthConfiguration() { InputStream inputStream = null; try { - URL url = new URL(baseUrl); - urlConnection = (HttpURLConnection) url.openConnection(); + + urlConnection = getHttpURLConnection(baseUrl); int statusCode = urlConnection.getResponseCode(); if (statusCode == HttpStatus.SC_OK) { diff --git a/opensrp-app/src/main/java/org/smartregister/view/fragment/BaseRegisterFragment.java b/opensrp-app/src/main/java/org/smartregister/view/fragment/BaseRegisterFragment.java index 08e268687..82f4fbafd 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/fragment/BaseRegisterFragment.java +++ b/opensrp-app/src/main/java/org/smartregister/view/fragment/BaseRegisterFragment.java @@ -1,5 +1,6 @@ package org.smartregister.view.fragment; +import android.content.Context; import android.os.Bundle; import android.support.annotation.LayoutRes; import android.support.annotation.Nullable; @@ -393,23 +394,28 @@ public void onSyncComplete(FetchStatus fetchStatus) { refreshSyncStatusViews(fetchStatus); } + @VisibleForTesting + protected void showShortToast(Context context, String message) { + Utils.showShortToast(context, message); + } + @VisibleForTesting protected void refreshSyncStatusViews(FetchStatus fetchStatus) { if (isSyncing()) { - Utils.showShortToast(getActivity(), getActivity().getString(R.string.syncing)); + showShortToast(getActivity(), getActivity().getString(R.string.syncing)); Timber.i(getActivity().getString(R.string.syncing)); refreshSyncProgressSpinner(); } else { if (fetchStatus != null) { if (fetchStatus.equals(FetchStatus.fetchedFailed)) { if (fetchStatus.displayValue().equals(ResponseErrorStatus.malformed_url.name())) { - Utils.showShortToast(getActivity(), getActivity().getString(R.string.sync_failed_malformed_url)); + showShortToast(getActivity(), getActivity().getString(R.string.sync_failed_malformed_url)); Timber.i(getActivity().getString(R.string.sync_failed_malformed_url)); } else if (fetchStatus.displayValue().equals(ResponseErrorStatus.timeout.name())) { - Utils.showShortToast(getActivity(), getActivity().getString(R.string.sync_failed_timeout_error)); + showShortToast(getActivity(), getActivity().getString(R.string.sync_failed_timeout_error)); Timber.i(getActivity().getString(R.string.sync_failed_timeout_error)); } else { - Utils.showShortToast(getActivity(), getActivity().getString(R.string.sync_failed)); + showShortToast(getActivity(), getActivity().getString(R.string.sync_failed)); Timber.i(getActivity().getString(R.string.sync_failed)); } refreshSyncProgressSpinner(); @@ -417,11 +423,11 @@ protected void refreshSyncStatusViews(FetchStatus fetchStatus) { setRefreshList(true); renderView(); - Utils.showShortToast(getActivity(), getActivity().getString(R.string.sync_complete)); + showShortToast(getActivity(), getActivity().getString(R.string.sync_complete)); Timber.i(getActivity().getString(R.string.sync_complete)); } else if (fetchStatus.equals(FetchStatus.noConnection)) { - Utils.showShortToast(getActivity(), getActivity().getString(R.string.sync_failed_no_internet)); + showShortToast(getActivity(), getActivity().getString(R.string.sync_failed_no_internet)); Timber.i(getActivity().getString(R.string.sync_failed_no_internet)); refreshSyncProgressSpinner(); } else { diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index f97d7c222..9782a9e75 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -2,8 +2,12 @@ import android.accounts.Account; import android.accounts.AccountManager; +import android.content.SharedPreferences; import android.util.Base64; +import com.google.common.io.BaseEncoding; + +import org.apache.commons.io.IOUtils; import org.json.JSONObject; import org.junit.Assert; import org.junit.Before; @@ -12,31 +16,43 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import org.smartregister.AllConstants; import org.smartregister.Context; import org.smartregister.CoreLibrary; import org.smartregister.DristhiConfiguration; import org.smartregister.SyncConfiguration; import org.smartregister.account.AccountAuthenticatorXml; +import org.smartregister.account.AccountConfiguration; import org.smartregister.account.AccountHelper; +import org.smartregister.account.AccountResponse; import org.smartregister.domain.LoginResponse; import org.smartregister.domain.ProfileImage; import org.smartregister.domain.Response; import org.smartregister.domain.ResponseStatus; -import org.smartregister.repository.AllSettings; import org.smartregister.repository.AllSharedPreferences; import java.io.File; import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.net.URL; import java.util.HashMap; +import java.util.List; @RunWith(PowerMockRunner.class) -@PrepareForTest({Base64.class, File.class, FileInputStream.class, Context.class, AccountHelper.class, CoreLibrary.class}) +@PrepareForTest({Base64.class, File.class, FileInputStream.class, Context.class, AccountHelper.class, CoreLibrary.class, IOUtils.class}) public class HTTPAgentTest { @Mock private android.content.Context context; @@ -45,10 +61,10 @@ public class HTTPAgentTest { private Context openSrpContext; @Mock - private AllSettings allSettings; + private AllSharedPreferences allSharedPreferences; @Mock - private AllSharedPreferences allSharedPreferences; + private SharedPreferences sharedPreferences; @Mock private DristhiConfiguration dristhiConfiguration; @@ -71,10 +87,38 @@ public class HTTPAgentTest { @Mock private SyncConfiguration syncConfiguration; + @Mock + private HttpURLConnection httpURLConnection; + + @Mock + private OutputStream outputStream; + + @Mock + private InputStream inputStream; + + @Mock + private InputStream errorStream; + @Rule private TemporaryFolder folder = new TemporaryFolder(); private HTTPAgent httpAgent; + private static final String TEST_USERNAME = "demo"; + private static final String TEST_PASSWORD = "password"; + private static final String TEST_TOKEN_ENDPOINT = "https://my-server.com/oauth/token"; + private static final String SECURE_RESOURCE_ENDPOINT = "https://my-server.com/my/secure/resource"; + private static final String KEYClOAK_CONFIGURATION_ENDPOINT = "https://my-server.com/rest/config/keycloak"; + + private final String SAMPLE_TEST_TOKEN = "sample-test-token"; + private final String SAMPLE_REFRESH_TOKEN = "sample-refresh-token"; + private static final String TEST_CLIENT_ID = "my-client-id"; + private static final String TEST_CLIENT_SECRET = "my-client-secret"; + private static final String TOKEN_REQUEST_SERVER_RESPONSE = "{\r\naccess_token:\"1r9A8zi5E3r@Zz\",\r\ntoken_type: \"bearer\",\r\nrefresh_token: \"text_token\",\r\nexpires_in: 3600,\r\nrefresh_expires_in: 36000,\r\nscope: \"read write trust\"\r\n\r\n}"; + private static final String TOKEN_BAD_REQUEST_SERVER_RESPONSE = "{status_code:400,\"error\":\"invalid_grant\",\"error_description\":\"Code not valid\"}"; + private static final String TOKEN_INTERNAL_SERVER_RESPONSE = "{status_code:500,\"error\":\"internal server error\",\"error_description\":\"Oops, something went wrong\"}"; + private static final String OAUTH_CONFIGURATION_SERVER_RESPONSE = "{\"issuer\":\"https://my-server.com/oauth/issuer\",\r\n\"authorization_endpoint\": \"https://my-server.com/oauth/auth\",\r\n\"token_endpoint\": \"https://my-server.com/oauth/token\",\r\n\"grant_types_supported\":[\"authorization code\",\"implicit\",\"password\"]\r\n}"; + private static final String FETCH_DATA_REQUEST_SERVER_RESPONSE = "{status:{\"response_status\":\"success\"},payload: \"My secure resources from the server\"\r\n\r\n}"; + private static final String SAMPLE_POST_REQUEST_PAYLOAD = "{\"payload\":\"My POST Payload\"}"; @Before public void setUp() { @@ -97,6 +141,8 @@ public void setUp() { PowerMockito.when(AccountHelper.getOauthAccountByType(accountAuthenticatorXml.getAccountType())).thenReturn(account); httpAgent = new HTTPAgent(context, allSharedPreferences, dristhiConfiguration); + httpAgent.setConnectTimeout(60000); + httpAgent.setReadTimeout(60000); } @Test @@ -155,14 +201,14 @@ public void testUrlCanBeAccessWithGivenCredentialsGivenEmptyResp() { @Test public void testfetchWithCredentialsFailsGivenWrongUrl() { - Response resp = httpAgent.fetchWithCredentials("wrong.url","sample-test-token"); + Response resp = httpAgent.fetchWithCredentials("wrong.url", SAMPLE_TEST_TOKEN); Assert.assertEquals(ResponseStatus.failure, resp.status()); } @Test public void testfetchWithCredentialsPassesGivenCorrectUrl() { PowerMockito.mockStatic(Base64.class); - Response resp = httpAgent.fetchWithCredentials("https://google.com","sample-test-token"); + Response resp = httpAgent.fetchWithCredentials("https://google.com", SAMPLE_TEST_TOKEN); Assert.assertEquals(ResponseStatus.success, resp.status()); } @@ -194,4 +240,324 @@ public void testPostWithJsonResponse() { Response resp = httpAgent.postWithJsonResponse("http://www.mocky.io/v2/5e54d9333100006300eb33a8", jObject.toString()); Assert.assertEquals(ResponseStatus.success, resp.status()); } + + + @Test + public void testOauth2authenticateCreatesUrlConnectionWithCorrectParameters() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_TOKEN_ENDPOINT); + Mockito.doReturn(TEST_CLIENT_ID).when(syncConfiguration).getOauthClientId(); + Mockito.doReturn(TEST_CLIENT_SECRET).when(syncConfiguration).getOauthClientSecret(); + + Mockito.doReturn(outputStream).when(httpURLConnection).getOutputStream(); + Mockito.doReturn(inputStream).when(httpURLConnection).getInputStream(); + Mockito.doReturn(HttpURLConnection.HTTP_OK).when(httpURLConnection).getResponseCode(); + + PowerMockito.mockStatic(IOUtils.class); + PowerMockito.when(IOUtils.toString(inputStream)).thenReturn(TOKEN_REQUEST_SERVER_RESPONSE); + + + AccountResponse accountResponse = httpAgentSpy.oauth2authenticate(TEST_USERNAME, TEST_PASSWORD, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, TEST_TOKEN_ENDPOINT); + + Assert.assertNotNull(accountResponse); + Assert.assertEquals(200, accountResponse.getStatus()); + Assert.assertEquals("1r9A8zi5E3r@Zz", accountResponse.getAccessToken()); + Assert.assertEquals("bearer", accountResponse.getTokenType()); + Assert.assertEquals("text_token", accountResponse.getRefreshToken()); + Assert.assertEquals(Integer.valueOf("3600"), accountResponse.getExpiresIn()); + Assert.assertEquals(Integer.valueOf("36000"), accountResponse.getRefreshExpiresIn()); + Assert.assertEquals("read write trust", accountResponse.getScope()); + + + Mockito.verify(httpURLConnection).setConnectTimeout(60000); + Mockito.verify(httpURLConnection).setReadTimeout(60000); + + String requestParams = "&grant_type=" + AccountHelper.OAUTH.GRANT_TYPE.PASSWORD + "&username=" + TEST_USERNAME + "&password=" + TEST_PASSWORD + "&client_id=" + TEST_CLIENT_ID + "&client_secret=" + TEST_CLIENT_SECRET; + + Mockito.verify(httpURLConnection).setFixedLengthStreamingMode(requestParams.getBytes().length); + Mockito.verify(httpURLConnection).setDoOutput(true); + Mockito.verify(httpURLConnection).setInstanceFollowRedirects(false); + Mockito.verify(httpURLConnection).setRequestMethod("POST"); + Mockito.verify(httpURLConnection).setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + Mockito.verify(httpURLConnection).setRequestProperty("charset", "utf-8"); + Mockito.verify(httpURLConnection).setRequestProperty(ArgumentMatchers.eq("Content-Length"), ArgumentMatchers.anyString()); + Mockito.verify(httpURLConnection).setUseCaches(false); + final String base64Auth = BaseEncoding.base64().encode(new String(TEST_CLIENT_ID + ":" + TEST_CLIENT_SECRET).getBytes()); + Mockito.verify(httpURLConnection).setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + base64Auth); + Mockito.verify(httpURLConnection).setInstanceFollowRedirects(false); + + } + + @Test + public void testOauth2authenticateReturnsCorrectResponseForBadRequest() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_TOKEN_ENDPOINT); + Mockito.doReturn(TEST_CLIENT_ID).when(syncConfiguration).getOauthClientId(); + Mockito.doReturn(TEST_CLIENT_SECRET).when(syncConfiguration).getOauthClientSecret(); + + Mockito.doReturn(outputStream).when(httpURLConnection).getOutputStream(); + Mockito.doReturn(inputStream).when(httpURLConnection).getInputStream(); + Mockito.doReturn(errorStream).when(httpURLConnection).getErrorStream(); + Mockito.doReturn(HttpURLConnection.HTTP_BAD_REQUEST).when(httpURLConnection).getResponseCode(); + + PowerMockito.mockStatic(IOUtils.class); + PowerMockito.when(IOUtils.toString(errorStream)).thenReturn(TOKEN_BAD_REQUEST_SERVER_RESPONSE); + + AccountResponse accountResponse = httpAgentSpy.oauth2authenticate(TEST_USERNAME, TEST_PASSWORD, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, TEST_TOKEN_ENDPOINT); + + Assert.assertNotNull(accountResponse); + Assert.assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, accountResponse.getStatus()); + Assert.assertNotNull(accountResponse.getAccountError()); + Assert.assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, accountResponse.getAccountError().getStatusCode()); + Assert.assertEquals("Code not valid", accountResponse.getAccountError().getErrorDescription()); + Assert.assertEquals("invalid_grant", accountResponse.getAccountError().getError()); + + } + + @Test + public void testOauth2authenticateReturnsCorrectAccountErrorResponseForMalformedURL() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_TOKEN_ENDPOINT); + Mockito.doReturn(TEST_CLIENT_ID).when(syncConfiguration).getOauthClientId(); + Mockito.doReturn(TEST_CLIENT_SECRET).when(syncConfiguration).getOauthClientSecret(); + + Mockito.doReturn(outputStream).when(httpURLConnection).getOutputStream(); + Mockito.doReturn(inputStream).when(httpURLConnection).getInputStream(); + + Mockito.doThrow(new MalformedURLException()).when(httpURLConnection).getResponseCode(); + + AccountResponse response = httpAgentSpy.oauth2authenticate(TEST_USERNAME, TEST_PASSWORD, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, TEST_TOKEN_ENDPOINT); + Assert.assertNotNull(response); + Assert.assertNotNull(response.getAccountError()); + Assert.assertEquals(0, response.getAccountError().getStatusCode()); + Assert.assertEquals(LoginResponse.MALFORMED_URL.name(), response.getAccountError().getError()); + + } + + @Test + public void testOauth2authenticateReturnsCorrectAccountErrorResponseForSocketTimeout() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_TOKEN_ENDPOINT); + Mockito.doReturn(TEST_CLIENT_ID).when(syncConfiguration).getOauthClientId(); + Mockito.doReturn(TEST_CLIENT_SECRET).when(syncConfiguration).getOauthClientSecret(); + + Mockito.doReturn(outputStream).when(httpURLConnection).getOutputStream(); + Mockito.doReturn(inputStream).when(httpURLConnection).getInputStream(); + + Mockito.doThrow(new SocketTimeoutException()).when(httpURLConnection).getResponseCode(); + + AccountResponse response = httpAgentSpy.oauth2authenticate(TEST_USERNAME, TEST_PASSWORD, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, TEST_TOKEN_ENDPOINT); + Assert.assertNotNull(response); + Assert.assertNotNull(response.getAccountError()); + Assert.assertEquals(0, response.getAccountError().getStatusCode()); + Assert.assertEquals(LoginResponse.TIMEOUT.name(), response.getAccountError().getError()); + + } + + @Test + public void testOauth2authenticateReturnsCorrectAccountErrorResponseForIOException() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_TOKEN_ENDPOINT); + Mockito.doReturn(TEST_CLIENT_ID).when(syncConfiguration).getOauthClientId(); + Mockito.doReturn(TEST_CLIENT_SECRET).when(syncConfiguration).getOauthClientSecret(); + + Mockito.doReturn(outputStream).when(httpURLConnection).getOutputStream(); + Mockito.doReturn(inputStream).when(httpURLConnection).getInputStream(); + + Mockito.doThrow(new IOException()).when(httpURLConnection).getResponseCode(); + + AccountResponse response = httpAgentSpy.oauth2authenticate(TEST_USERNAME, TEST_PASSWORD, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, TEST_TOKEN_ENDPOINT); + Assert.assertNotNull(response); + Assert.assertNotNull(response.getAccountError()); + Assert.assertEquals(0, response.getAccountError().getStatusCode()); + Assert.assertEquals(LoginResponse.NO_INTERNET_CONNECTIVITY.name(), response.getAccountError().getError()); + + } + + @Test + public void testOauth2authenticateReturnsNonNullAccountErrorResponseForRandomException() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_TOKEN_ENDPOINT); + Mockito.doReturn(TEST_CLIENT_ID).when(syncConfiguration).getOauthClientId(); + Mockito.doReturn(TEST_CLIENT_SECRET).when(syncConfiguration).getOauthClientSecret(); + + Mockito.doReturn(errorStream).when(httpURLConnection).getErrorStream(); + Mockito.doReturn(outputStream).when(httpURLConnection).getOutputStream(); + + Mockito.doReturn(HttpURLConnection.HTTP_INTERNAL_ERROR).when(httpURLConnection).getResponseCode(); + + PowerMockito.mockStatic(IOUtils.class); + PowerMockito.when(IOUtils.toString(errorStream)).thenReturn(TOKEN_INTERNAL_SERVER_RESPONSE); + + AccountResponse response = httpAgentSpy.oauth2authenticate(TEST_USERNAME, TEST_PASSWORD, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, TEST_TOKEN_ENDPOINT); + Assert.assertNotNull(response); + Assert.assertNotNull(response.getAccountError()); + Assert.assertEquals(HttpURLConnection.HTTP_INTERNAL_ERROR, response.getAccountError().getStatusCode()); + Assert.assertNotNull(response.getAccountError().getError()); + Assert.assertEquals("Oops, something went wrong", response.getAccountError().getErrorDescription()); + Assert.assertEquals("internal server error", response.getAccountError().getError()); + + } + + @Test + public void testFetchOAuthConfigurationProcessesConfigurationResponseCorrectly() throws Exception { + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn("https://my-server.com/").when(dristhiConfiguration).dristhiBaseURL(); + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(KEYClOAK_CONFIGURATION_ENDPOINT); + + Mockito.doReturn(inputStream).when(httpURLConnection).getInputStream(); + Mockito.doReturn(HttpURLConnection.HTTP_OK).when(httpURLConnection).getResponseCode(); + + PowerMockito.mockStatic(IOUtils.class); + PowerMockito.when(IOUtils.toString(inputStream)).thenReturn(OAUTH_CONFIGURATION_SERVER_RESPONSE); + + AccountConfiguration accountConfiguration = httpAgentSpy.fetchOAuthConfiguration(); + Assert.assertNotNull(accountConfiguration); + Assert.assertEquals("https://my-server.com/oauth/auth", accountConfiguration.getAuthorizationEndpoint()); + Assert.assertEquals("https://my-server.com/oauth/issuer", accountConfiguration.getIssuerEndpoint()); + Assert.assertEquals(TEST_TOKEN_ENDPOINT, accountConfiguration.getTokenEndpoint()); + + List grantTypes = accountConfiguration.getGrantTypesSupported(); + Assert.assertNotNull(grantTypes); + Assert.assertEquals("authorization code", grantTypes.get(0)); + Assert.assertEquals("implicit", grantTypes.get(1)); + Assert.assertEquals("password", grantTypes.get(2)); + } + + @Test + public void testFetchInvalidatesCacheIfUnauthorizedAndReturnsCorrectResponse() throws Exception { + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(SECURE_RESOURCE_ENDPOINT); + Mockito.doReturn(errorStream).when(httpURLConnection).getErrorStream(); + Mockito.doReturn(HttpURLConnection.HTTP_UNAUTHORIZED).when(httpURLConnection).getResponseCode(); + + PowerMockito.mockStatic(IOUtils.class); + PowerMockito.when(IOUtils.toString(errorStream)).thenReturn(FETCH_DATA_REQUEST_SERVER_RESPONSE); + + PowerMockito.mockStatic(AccountHelper.class); + PowerMockito.when(AccountHelper.getCachedOAuthToken(accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); + + Response response = httpAgentSpy.fetch(SECURE_RESOURCE_ENDPOINT); + Assert.assertNotNull(response); + Assert.assertEquals(ResponseStatus.valueOf("success"), response.status()); + Assert.assertEquals(FETCH_DATA_REQUEST_SERVER_RESPONSE, response.payload()); + + PowerMockito.verifyStatic(AccountHelper.class); + AccountHelper.invalidateAuthToken(accountAuthenticatorXml.getAccountType(), SAMPLE_TEST_TOKEN); + + } + + @Test + public void testPostInvokesInvalidateCacheIfUnauthorizedOnFirstAttempt() throws Exception { + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(SECURE_RESOURCE_ENDPOINT); + Mockito.doReturn(errorStream).when(httpURLConnection).getErrorStream(); + Mockito.doReturn(HttpURLConnection.HTTP_UNAUTHORIZED).when(httpURLConnection).getResponseCode(); + + PowerMockito.mockStatic(IOUtils.class); + PowerMockito.when(IOUtils.toString(errorStream)).thenReturn(FETCH_DATA_REQUEST_SERVER_RESPONSE); + + PowerMockito.mockStatic(AccountHelper.class); + PowerMockito.when(AccountHelper.getCachedOAuthToken(accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).generatePostRequest(SECURE_RESOURCE_ENDPOINT, SAMPLE_POST_REQUEST_PAYLOAD); + + httpAgentSpy.post(SECURE_RESOURCE_ENDPOINT, SAMPLE_POST_REQUEST_PAYLOAD); + + Mockito.verify(httpAgentSpy).invalidateExpiredCachedAccessToken(); + + } + + @Test + public void testFetchWithCredentialsInvokesInvalidateCacheIfUnauthorizedOnFirstAttempt() throws Exception { + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(SECURE_RESOURCE_ENDPOINT); + Mockito.doReturn(errorStream).when(httpURLConnection).getErrorStream(); + Mockito.doReturn(HttpURLConnection.HTTP_UNAUTHORIZED).when(httpURLConnection).getResponseCode(); + + PowerMockito.mockStatic(IOUtils.class); + PowerMockito.when(IOUtils.toString(errorStream)).thenReturn(FETCH_DATA_REQUEST_SERVER_RESPONSE); + + PowerMockito.mockStatic(AccountHelper.class); + PowerMockito.when(AccountHelper.getCachedOAuthToken(accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); + + Response response = httpAgentSpy.fetchWithCredentials(SECURE_RESOURCE_ENDPOINT, SAMPLE_TEST_TOKEN); + Assert.assertNotNull(response); + + PowerMockito.verifyStatic(AccountHelper.class); + AccountHelper.invalidateAuthToken(accountAuthenticatorXml.getAccountType(), SAMPLE_TEST_TOKEN); + + } + + @Test + public void testOauth2authenticateRefreshTokenInvokesOauth2authenticateCoreWithCorrectParams() throws Exception { + + Mockito.doReturn(sharedPreferences).when(allSharedPreferences).getPreferences(); + Mockito.doReturn(TEST_TOKEN_ENDPOINT).when(sharedPreferences).getString(AccountHelper.CONFIGURATION_CONSTANTS.TOKEN_ENDPOINT_URL, ""); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + AccountResponse accountResponse = Mockito.mock(AccountResponse.class); + + Mockito.doReturn(accountResponse).when(httpAgentSpy).oauth2authenticateCore(ArgumentMatchers.any(StringBuilder.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()); + + ArgumentCaptor requestParamStringBuilder = ArgumentCaptor.forClass(StringBuilder.class); + ArgumentCaptor grantType = ArgumentCaptor.forClass(String.class); + ArgumentCaptor tokenEndPoint = ArgumentCaptor.forClass(String.class); + + httpAgentSpy.oauth2authenticateRefreshToken(SAMPLE_REFRESH_TOKEN); + + Mockito.verify(httpAgentSpy).oauth2authenticateCore(requestParamStringBuilder.capture(), grantType.capture(), tokenEndPoint.capture()); + + String capturedRefreshTokenRequestValue = requestParamStringBuilder.getValue().toString(); + String capturedGrantTypeValue = grantType.getValue(); + String capturedTokenEndpointValue = tokenEndPoint.getValue(); + + Assert.assertEquals("&refresh_token=" + SAMPLE_REFRESH_TOKEN, capturedRefreshTokenRequestValue); + + Assert.assertEquals(AccountHelper.OAUTH.GRANT_TYPE.REFRESH_TOKEN, capturedGrantTypeValue); + Assert.assertEquals(TEST_TOKEN_ENDPOINT, capturedTokenEndpointValue); + + + } } diff --git a/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java b/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java index 2c2fac785..d4daea9bc 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java @@ -26,22 +26,27 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.powermock.core.classloader.annotations.PrepareForTest; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; import org.robolectric.util.ReflectionHelpers; import org.smartregister.AllConstants; import org.smartregister.BaseUnitTest; import org.smartregister.Context; +import org.smartregister.CoreLibrary; import org.smartregister.R; import org.smartregister.cursoradapter.RecyclerViewPaginatedAdapter; import org.smartregister.domain.FetchStatus; import org.smartregister.domain.ResponseErrorStatus; +import org.smartregister.util.AppProperties; import org.smartregister.view.activity.SecuredNativeSmartRegisterActivity; import org.smartregister.view.contract.BaseRegisterFragmentContract; /** * Created by ndegwamartin on 2020-04-28. */ + +@PrepareForTest({CoreLibrary.class}) public class BaseRegisterFragmentTest extends BaseUnitTest { private BaseRegisterFragment baseRegisterFragment; @@ -102,6 +107,9 @@ public class BaseRegisterFragmentTest extends BaseUnitTest { @Mock private Resources resources; + @Mock + private AppProperties appProperties; + @Before public void setUp() { @@ -120,6 +128,13 @@ public void setUp() { AppCompatActivity activitySpy = Mockito.spy(activity); Mockito.doReturn(activitySpy).when(baseRegisterFragment).getActivity(); + + CoreLibrary.init(opensrpContext); + Mockito.doReturn(appProperties).when(opensrpContext).getAppProperties(); + + Mockito.doReturn(opensrpContext).when(baseRegisterFragment).context(); + + Mockito.doNothing().when(baseRegisterFragment).showShortToast(ArgumentMatchers.eq(activitySpy), ArgumentMatchers.anyString()); } @Test @@ -227,7 +242,6 @@ public void setSearchTermInitsCorrectValue() { } @Test - @Ignore public void assertOnQRCodeSucessfullyScannedInvokesFilterWithCorrectParams() { String OPENSRP_ID = "8232-372-8L"; @@ -402,15 +416,15 @@ public void testRefreshSyncStatusViewsWithSyncingTrue() { View parentLayout = LayoutInflater.from(RuntimeEnvironment.application.getApplicationContext()).inflate(R.layout.fragment_base_register, null, false); Mockito.doReturn(parentLayout).when(layoutInflater).inflate(R.layout.fragment_base_register, container, false); - ProgressBar syncProgressBar = parentLayout.findViewById(R.id.sync_progress_bar); + ProgressBar syncProgressBar = parentLayout.findViewById(R.id.sync_progress_bar); ImageView syncButton = parentLayout.findViewById(R.id.sync_refresh); ReflectionHelpers.setField(baseRegisterFragment, "syncProgressBar", syncProgressBar); ReflectionHelpers.setField(baseRegisterFragment, "syncButton", syncButton); baseRegisterFragment.refreshSyncStatusViews(FetchStatus.fetchStarted); Mockito.verify(baseRegisterFragment).refreshSyncProgressSpinner(); - Assert.assertEquals(View.VISIBLE,syncProgressBar.getVisibility()); - Assert.assertEquals(View.GONE,syncButton.getVisibility()); + Assert.assertEquals(View.VISIBLE, syncProgressBar.getVisibility()); + Assert.assertEquals(View.GONE, syncButton.getVisibility()); } @Test @@ -485,7 +499,7 @@ public void testRefreshSyncStatusViewsWithSyncingFalseFetchStatusNoConnection() View Layout = LayoutInflater.from(RuntimeEnvironment.application.getApplicationContext()).inflate(R.layout.fragment_base_register, null, false); Mockito.doReturn(Layout).when(layoutInflater).inflate(R.layout.fragment_base_register, container, false); - ProgressBar progressBar = Layout.findViewById(R.id.sync_progress_bar); + ProgressBar progressBar = Layout.findViewById(R.id.sync_progress_bar); ImageView imageView = Layout.findViewById(R.id.sync_refresh); ReflectionHelpers.setField(baseRegisterFragment, "syncProgressBar", progressBar); @@ -493,8 +507,8 @@ public void testRefreshSyncStatusViewsWithSyncingFalseFetchStatusNoConnection() baseRegisterFragment.refreshSyncStatusViews(FetchStatus.noConnection); Mockito.verify(baseRegisterFragment).refreshSyncProgressSpinner(); - Assert.assertEquals(View.GONE,progressBar.getVisibility()); - Assert.assertEquals(View.VISIBLE,imageView.getVisibility()); + Assert.assertEquals(View.GONE, progressBar.getVisibility()); + Assert.assertEquals(View.VISIBLE, imageView.getVisibility()); } @Test From a7477ef0323ef65934cd9df5a54254b7ec1657c0 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 2 Jun 2020 10:37:28 +0300 Subject: [PATCH 16/70] Account Helper Unit test --- .../account/AccountHelperTest.java | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 opensrp-app/src/test/java/org/smartregister/account/AccountHelperTest.java diff --git a/opensrp-app/src/test/java/org/smartregister/account/AccountHelperTest.java b/opensrp-app/src/test/java/org/smartregister/account/AccountHelperTest.java new file mode 100644 index 000000000..307e058dd --- /dev/null +++ b/opensrp-app/src/test/java/org/smartregister/account/AccountHelperTest.java @@ -0,0 +1,93 @@ +package org.smartregister.account; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.powermock.reflect.Whitebox; +import org.smartregister.BaseUnitTest; + +import java.io.IOException; + +/** + * Created by ndegwamartin on 26/05/2020. + */ + +public class AccountHelperTest extends BaseUnitTest { + private static final String CORE_ACCOUNT_NAME = "demo"; + private static final String CORE_ACCOUNT_TYPE = "org.smartregister.core"; + private static final String TEST_KEY = "testKey"; + private static final String TEST_VALUE = "random-test-value"; + private static final String AUTH_TOKEN_TYPE = "My Admin Token Type"; + private static final String TEST_TOKEN_VALUE = "sample-token"; + + @Mock + private AccountManager accountManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + Account[] accounts = {new Account(CORE_ACCOUNT_NAME, CORE_ACCOUNT_TYPE)}; + Mockito.doReturn(accounts).when(accountManager).getAccountsByType(CORE_ACCOUNT_TYPE); + + Whitebox.setInternalState(AccountHelper.class, "accountManager", accountManager); + } + + @Test + public void testGetOauthAccountByType() { + + Account account = AccountHelper.getOauthAccountByType(CORE_ACCOUNT_TYPE); + Assert.assertNotNull(account); + Assert.assertEquals(CORE_ACCOUNT_NAME, account.name); + } + + @Test + public void testGetAccountManagerValue() { + + Whitebox.setInternalState(AccountHelper.class, "accountManager", accountManager); + + Mockito.doReturn(TEST_VALUE).when(accountManager).getUserData(ArgumentMatchers.any(Account.class), ArgumentMatchers.eq(TEST_KEY)); + + String value = AccountHelper.getAccountManagerValue(TEST_KEY, CORE_ACCOUNT_TYPE); + Assert.assertNotNull(value); + Assert.assertEquals(TEST_VALUE, value); + } + + @Test + public void testGetOAuthToken() throws AuthenticatorException, OperationCanceledException, IOException { + + Mockito.doReturn(TEST_TOKEN_VALUE).when(accountManager).blockingGetAuthToken(new Account(CORE_ACCOUNT_NAME, CORE_ACCOUNT_TYPE), AUTH_TOKEN_TYPE, true); + String myToken = AccountHelper.getOAuthToken(CORE_ACCOUNT_TYPE, AUTH_TOKEN_TYPE); + Assert.assertNotNull(myToken); + Assert.assertEquals(TEST_TOKEN_VALUE, myToken); + } + + @Test + public void testInvalidateAuthToken() { + + AccountHelper.invalidateAuthToken(CORE_ACCOUNT_TYPE, TEST_TOKEN_VALUE); + Mockito.verify(accountManager).invalidateAuthToken(CORE_ACCOUNT_TYPE, TEST_TOKEN_VALUE); + } + + @Test + public void testGetCachedOAuthToken() { + + Account account = new Account(CORE_ACCOUNT_NAME, CORE_ACCOUNT_TYPE); + Mockito.doReturn(TEST_TOKEN_VALUE).when(accountManager).peekAuthToken(account, AUTH_TOKEN_TYPE); + + String cachedAuthToken = AccountHelper.getCachedOAuthToken(CORE_ACCOUNT_TYPE, AUTH_TOKEN_TYPE); + + Mockito.verify(accountManager).peekAuthToken(account, AUTH_TOKEN_TYPE); + Assert.assertEquals(TEST_TOKEN_VALUE, cachedAuthToken); + } + +} From 9de9d9410c57ae743de96e3809ac93d603c45cfc Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 2 Jun 2020 14:06:55 +0300 Subject: [PATCH 17/70] HTTPAgent Unit tests - Add more unit test cases --- .../org/smartregister/service/HTTPAgent.java | 12 +- .../smartregister/service/HTTPAgentTest.java | 350 +++++++++++++++++- .../util/LoginResponseTestData.java | 172 +++++++++ 3 files changed, 528 insertions(+), 6 deletions(-) create mode 100644 opensrp-app/src/test/java/org/smartregister/util/LoginResponseTestData.java diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index 725240ae0..70d5aad10 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -235,11 +235,13 @@ public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, Strin urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, basicAuth); int statusCode = urlConnection.getResponseCode(); InputStream inputStream; + String responseString = ""; if (statusCode >= HttpStatus.SC_BAD_REQUEST) inputStream = urlConnection.getErrorStream(); else inputStream = urlConnection.getInputStream(); - String responseString = IOUtils.toString(inputStream); + if (inputStream != null) + responseString = IOUtils.toString(inputStream); if (statusCode == HttpStatus.SC_OK) { Timber.d("response String: %s using request url %s", responseString, url); @@ -666,11 +668,15 @@ public LoginResponse fetchUserDetails(String requestURL, String oauthAccessToken int statusCode = urlConnection.getResponseCode(); InputStream inputStream; + String responseString = null; if (statusCode >= HttpStatus.SC_BAD_REQUEST) inputStream = urlConnection.getErrorStream(); else inputStream = urlConnection.getInputStream(); - String responseString = IOUtils.toString(inputStream); + + if (inputStream != null) + responseString = IOUtils.toString(inputStream); + if (statusCode == HttpStatus.SC_OK) { Timber.d("response String: %s using request url %s", responseString, url); @@ -804,7 +810,7 @@ public boolean verifyAuthorization() { urlConnection = initializeHttp(baseUrl, true); Timber.i("User not authorized. User access was revoked, will log off user"); - return HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode() ? false : true; + return false; } else if (statusCode != HttpStatus.SC_OK) { Timber.w("Error occurred verifying authorization, User will not be logged off"); } else { diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index 9782a9e75..c76839219 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -11,7 +11,6 @@ import org.json.JSONObject; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -38,6 +37,7 @@ import org.smartregister.domain.Response; import org.smartregister.domain.ResponseStatus; import org.smartregister.repository.AllSharedPreferences; +import org.smartregister.util.LoginResponseTestData; import java.io.File; import java.io.FileInputStream; @@ -51,6 +51,8 @@ import java.util.HashMap; import java.util.List; +import javax.net.ssl.HttpsURLConnection; + @RunWith(PowerMockRunner.class) @PrepareForTest({Base64.class, File.class, FileInputStream.class, Context.class, AccountHelper.class, CoreLibrary.class, IOUtils.class}) public class HTTPAgentTest { @@ -90,6 +92,9 @@ public class HTTPAgentTest { @Mock private HttpURLConnection httpURLConnection; + @Mock + private HttpsURLConnection httpsURLConnection; + @Mock private OutputStream outputStream; @@ -105,9 +110,11 @@ public class HTTPAgentTest { private HTTPAgent httpAgent; private static final String TEST_USERNAME = "demo"; private static final String TEST_PASSWORD = "password"; + public static final String TEST_BASE_URL = "https://my-server.com/"; private static final String TEST_TOKEN_ENDPOINT = "https://my-server.com/oauth/token"; private static final String SECURE_RESOURCE_ENDPOINT = "https://my-server.com/my/secure/resource"; private static final String KEYClOAK_CONFIGURATION_ENDPOINT = "https://my-server.com/rest/config/keycloak"; + private static final String USER_DETAILS_ENDPOINT = "https://my-server.com/opensrp/security/authenticate"; private final String SAMPLE_TEST_TOKEN = "sample-test-token"; private final String SAMPLE_REFRESH_TOKEN = "sample-refresh-token"; @@ -192,7 +199,6 @@ public void testUrlCanBeAccessWithGivenCredentialsGivenWrongUrl() { } @Test - @Ignore public void testUrlCanBeAccessWithGivenCredentialsGivenEmptyResp() { PowerMockito.mockStatic(Base64.class); LoginResponse resp = httpAgent.urlCanBeAccessWithGivenCredentials("http://mockbin.org/bin/e42f7256-18b2-40b9-a20c-40fdc564d06f", "", ""); @@ -436,7 +442,7 @@ public void testFetchOAuthConfigurationProcessesConfigurationResponseCorrectly() HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); - Mockito.doReturn("https://my-server.com/").when(dristhiConfiguration).dristhiBaseURL(); + Mockito.doReturn(TEST_BASE_URL).when(dristhiConfiguration).dristhiBaseURL(); Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(KEYClOAK_CONFIGURATION_ENDPOINT); Mockito.doReturn(inputStream).when(httpURLConnection).getInputStream(); @@ -560,4 +566,342 @@ public void testOauth2authenticateRefreshTokenInvokesOauth2authenticateCoreWithC } + + @Test + public void testFetchUserDetailsConstructsCorrectResponse() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpsURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); + Mockito.doReturn(inputStream).when(httpsURLConnection).getInputStream(); + Mockito.doReturn(HttpURLConnection.HTTP_OK).when(httpsURLConnection).getResponseCode(); + + PowerMockito.mockStatic(IOUtils.class); + PowerMockito.when(IOUtils.toString(inputStream)).thenReturn(LoginResponseTestData.USER_DETAILS_REQUEST_SERVER_RESPONSE); + + LoginResponse loginResponse = httpAgentSpy.fetchUserDetails(USER_DETAILS_ENDPOINT, SAMPLE_TEST_TOKEN); + + Assert.assertNotNull(loginResponse); + Assert.assertNotNull(loginResponse.message()); + Assert.assertEquals("Login successful.", loginResponse.message()); + Assert.assertNotNull(loginResponse.payload()); + + Assert.assertNotNull(loginResponse.payload().user); + Assert.assertEquals("demo", loginResponse.payload().user.getUsername()); + Assert.assertEquals("Demo User", loginResponse.payload().user.getPreferredName()); + Assert.assertEquals("93c6526-6667-3333-a611112-f3b309999999", loginResponse.payload().user.getBaseEntityId()); + + Assert.assertNotNull(loginResponse.payload().time); + Assert.assertEquals("2020-06-02 08:21:40", loginResponse.payload().time.getTime()); + Assert.assertEquals("Africa/Nairobi", loginResponse.payload().time.getTimeZone()); + + Assert.assertNotNull(loginResponse.payload().locations); + Assert.assertNotNull(loginResponse.payload().locations.getLocationsHierarchy()); + + Assert.assertNotNull(loginResponse.payload().jurisdictions); + Assert.assertEquals(1, loginResponse.payload().jurisdictions.size()); + Assert.assertNotNull("Health Team Kasarani", loginResponse.payload().jurisdictions.get(0)); + + Assert.assertNotNull(loginResponse.payload().team); + Assert.assertEquals("93c6526-6667-3333-a611112-f3b309999999", loginResponse.payload().team.identifier); + Assert.assertEquals("93c6526-6667-3333-a611112-f3b309999999", loginResponse.payload().team.uuid); + + Assert.assertEquals("SUCCESS", loginResponse.name()); + + ArgumentCaptor headerKey = ArgumentCaptor.forClass(String.class); + ArgumentCaptor headerValue = ArgumentCaptor.forClass(String.class); + + Mockito.verify(httpsURLConnection).setRequestProperty(headerKey.capture(), headerValue.capture()); + String capturedKey = headerKey.getValue(); + String capturedValue = headerValue.getValue(); + + Assert.assertEquals(capturedKey, AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION); + Assert.assertEquals(capturedValue, AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " " + SAMPLE_TEST_TOKEN); + + } + + @Test + public void testFetchUserDetailsConstructsCorrectResponseForUnauthorizedRequests() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpsURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); + Mockito.doReturn(HttpURLConnection.HTTP_UNAUTHORIZED).when(httpsURLConnection).getResponseCode(); + + LoginResponse loginResponse = httpAgentSpy.fetchUserDetails(USER_DETAILS_ENDPOINT, SAMPLE_TEST_TOKEN); + + Assert.assertNotNull(loginResponse); + Assert.assertNotNull(loginResponse.message()); + Assert.assertEquals("Please check the credentials", loginResponse.message()); + Assert.assertNull(loginResponse.payload()); + + Assert.assertEquals("UNAUTHORIZED", loginResponse.name()); + + } + + @Test + public void testFetchUserDetailsConstructsCorrectResponseForRandomServerError() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpsURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); + + Mockito.doReturn(errorStream).when(httpsURLConnection).getInputStream(); + Mockito.doReturn(HttpURLConnection.HTTP_INTERNAL_ERROR).when(httpsURLConnection).getResponseCode(); + + PowerMockito.mockStatic(IOUtils.class); + PowerMockito.when(IOUtils.toString(errorStream)).thenReturn("

message Oops, something went wrong

"); + + LoginResponse loginResponse = httpAgentSpy.fetchUserDetails(USER_DETAILS_ENDPOINT, SAMPLE_TEST_TOKEN); + + Assert.assertNotNull(loginResponse); + Assert.assertNotNull(loginResponse.message()); + Assert.assertEquals("Dristhi login failed. Try later", loginResponse.message()); + Assert.assertNull(loginResponse.payload()); + + Assert.assertEquals("UNKNOWN_RESPONSE", loginResponse.name()); + + } + + + @Test + public void testFetchUserDetailsConstructsCorrectResponseForMalformedURLRequests() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpsURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); + Mockito.doThrow(new MalformedURLException()).when(httpsURLConnection).getResponseCode(); + + LoginResponse loginResponse = httpAgentSpy.fetchUserDetails(USER_DETAILS_ENDPOINT, SAMPLE_TEST_TOKEN); + + Assert.assertNotNull(loginResponse); + Assert.assertNotNull(loginResponse.message()); + Assert.assertEquals("Incorrect url", loginResponse.message()); + Assert.assertNull(loginResponse.payload()); + + Assert.assertEquals("MALFORMED_URL", loginResponse.name()); + + } + + @Test + public void testFetchUserDetailsConstructsCorrectResponseForConnectionTimedOutRequests() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpsURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); + Mockito.doThrow(new SocketTimeoutException()).when(httpsURLConnection).getResponseCode(); + + LoginResponse loginResponse = httpAgentSpy.fetchUserDetails(USER_DETAILS_ENDPOINT, SAMPLE_TEST_TOKEN); + + Assert.assertNotNull(loginResponse); + Assert.assertNotNull(loginResponse.message()); + Assert.assertEquals("The server could not be reached. Try again", loginResponse.message()); + Assert.assertNull(loginResponse.payload()); + + Assert.assertEquals("TIMEOUT", loginResponse.name()); + + } + + + @Test + public void testFetchUserDetailsConstructsCorrectResponseForRequestsWithNetworkConnectivity() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpsURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); + Mockito.doThrow(new IOException()).when(httpsURLConnection).getResponseCode(); + + LoginResponse loginResponse = httpAgentSpy.fetchUserDetails(USER_DETAILS_ENDPOINT, SAMPLE_TEST_TOKEN); + + Assert.assertNotNull(loginResponse); + Assert.assertNotNull(loginResponse.message()); + Assert.assertEquals("No internet connection. Please ensure data connectivity", loginResponse.message()); + Assert.assertNull(loginResponse.payload()); + + Assert.assertEquals("NO_INTERNET_CONNECTIVITY", loginResponse.name()); + + } + + + @Test + public void testVerifyAuthorizationReturnsTrueForAuthorizedResponse() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(TEST_BASE_URL).when(dristhiConfiguration).dristhiBaseURL(); + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(KEYClOAK_CONFIGURATION_ENDPOINT); + + Mockito.doReturn(HttpURLConnection.HTTP_OK).when(httpURLConnection).getResponseCode(); + + boolean isVerified = httpAgentSpy.verifyAuthorization(); + Assert.assertTrue(isVerified); + + } + + @Test + public void testVerifyAuthorizationReturnsFalseForUnauthorizedResponse() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(TEST_BASE_URL).when(dristhiConfiguration).dristhiBaseURL(); + Mockito.doReturn(TEST_USERNAME).when(allSharedPreferences).fetchRegisteredANM(); + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection("https://my-server.com/user-details?anm-id=" + TEST_USERNAME); + + Mockito.doReturn(HttpURLConnection.HTTP_UNAUTHORIZED).when(httpURLConnection).getResponseCode(); + + boolean isVerified = httpAgentSpy.verifyAuthorization(); + Assert.assertFalse(isVerified); + + } + + @Test + public void testUrlCanBeAccessWithGivenCredentialsReturnsUnauthorizedResponse() throws Exception { + + PowerMockito.mockStatic(Base64.class); + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(TEST_BASE_URL).when(dristhiConfiguration).dristhiBaseURL(); + Mockito.doReturn(TEST_USERNAME).when(allSharedPreferences).fetchRegisteredANM(); + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); + + Mockito.doReturn(HttpURLConnection.HTTP_UNAUTHORIZED).when(httpURLConnection).getResponseCode(); + + LoginResponse response = httpAgentSpy.urlCanBeAccessWithGivenCredentials(USER_DETAILS_ENDPOINT, TEST_USERNAME, TEST_PASSWORD); + Assert.assertNotNull(response); + Assert.assertNotNull(response.message()); + Assert.assertNull(response.payload()); + Assert.assertEquals("Please check the credentials", response.message()); + + } + + @Test + public void testUrlCanBeAccessWithGivenCredentialsReturnsErrorResponseForMalformedURL() throws Exception { + + PowerMockito.mockStatic(Base64.class); + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(TEST_BASE_URL).when(dristhiConfiguration).dristhiBaseURL(); + Mockito.doReturn(TEST_USERNAME).when(allSharedPreferences).fetchRegisteredANM(); + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); + + Mockito.doThrow(new MalformedURLException()).when(httpURLConnection).getResponseCode(); + + LoginResponse response = httpAgentSpy.urlCanBeAccessWithGivenCredentials(USER_DETAILS_ENDPOINT, TEST_USERNAME, TEST_PASSWORD); + Assert.assertNotNull(response); + Assert.assertNull(response.payload()); + Assert.assertNotNull(response.message()); + Assert.assertEquals(LoginResponse.MALFORMED_URL.name(), response.name()); + + } + + @Test + public void testUrlCanBeAccessWithGivenCredentialsReturnsCorrectErrorResponseForSocketTimeout() throws Exception { + + PowerMockito.mockStatic(Base64.class); + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(TEST_BASE_URL).when(dristhiConfiguration).dristhiBaseURL(); + Mockito.doReturn(TEST_USERNAME).when(allSharedPreferences).fetchRegisteredANM(); + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); + + Mockito.doThrow(new MalformedURLException()).when(httpURLConnection).getResponseCode(); + + + LoginResponse response = httpAgentSpy.urlCanBeAccessWithGivenCredentials(USER_DETAILS_ENDPOINT, TEST_USERNAME, TEST_PASSWORD); + Assert.assertNotNull(response); + Assert.assertNull(response.payload()); + Assert.assertNotNull(response.message()); + Assert.assertEquals(LoginResponse.MALFORMED_URL.name(), response.name()); + + } + + @Test + public void testUrlCanBeAccessWithGivenCredentialsReturnsCorrectErrorResponseErrorResponseForIOException() throws Exception { + + PowerMockito.mockStatic(Base64.class); + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(TEST_BASE_URL).when(dristhiConfiguration).dristhiBaseURL(); + Mockito.doReturn(TEST_USERNAME).when(allSharedPreferences).fetchRegisteredANM(); + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); + + Mockito.doThrow(new IOException()).when(httpURLConnection).getResponseCode(); + + + LoginResponse response = httpAgentSpy.urlCanBeAccessWithGivenCredentials(USER_DETAILS_ENDPOINT, TEST_USERNAME, TEST_PASSWORD); + Assert.assertNotNull(response); + Assert.assertNull(response.payload()); + Assert.assertNotNull(response.message()); + Assert.assertEquals(LoginResponse.NO_INTERNET_CONNECTIVITY.name(), response.name()); + + } + + @Test + public void testUrlCanBeAccessWithGivenCredentialsReturnsCorrectResponseForRandomServerError() throws Exception { + + PowerMockito.mockStatic(Base64.class); + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpsURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); + + Mockito.doReturn(errorStream).when(httpsURLConnection).getErrorStream(); + Mockito.doReturn(HttpURLConnection.HTTP_INTERNAL_ERROR).when(httpsURLConnection).getResponseCode(); + + PowerMockito.mockStatic(IOUtils.class); + PowerMockito.when(IOUtils.toString(errorStream)).thenReturn("

message Oops, something went wrong

"); + + LoginResponse loginResponse = httpAgentSpy.urlCanBeAccessWithGivenCredentials(USER_DETAILS_ENDPOINT, TEST_USERNAME, TEST_PASSWORD); + + Assert.assertNotNull(loginResponse); + Assert.assertNotNull(loginResponse.message()); + Assert.assertEquals("Oops, something went wrong", loginResponse.message()); + Assert.assertNull(loginResponse.payload()); + + Assert.assertEquals("CUSTOM_SERVER_RESPONSE", loginResponse.name()); + + } } diff --git a/opensrp-app/src/test/java/org/smartregister/util/LoginResponseTestData.java b/opensrp-app/src/test/java/org/smartregister/util/LoginResponseTestData.java new file mode 100644 index 000000000..8e9930607 --- /dev/null +++ b/opensrp-app/src/test/java/org/smartregister/util/LoginResponseTestData.java @@ -0,0 +1,172 @@ +package org.smartregister.util; + +/** + * Created by ndegwamartin on 02/06/2020. + */ +public final class LoginResponseTestData { + + public static final String USER_DETAILS_REQUEST_SERVER_RESPONSE = "{\n" + + " \"locations\":{\n" + + " \"locationsHierarchy\":{\n" + + " \"map\":{\n" + + " \"583j4bve4-9a36-459a-99fc-bc0356d99999\":{\n" + + " \"id\":\"583j4bve4-9a36-459a-99fc-bc0356d99999\",\n" + + " \"label\":\"Kenya\",\n" + + " \"node\":{\n" + + " \"locationId\":\"583j4bve4-9a36-459a-99fc-bc0356d99999\",\n" + + " \"name\":\"Kenya\",\n" + + " \"tags\":[\n" + + " \"Country\"\n" + + " ],\n" + + " \"voided\":false\n" + + " },\n" + + " \"children\":{\n" + + " \"3x3tsh58-e6db-4dsa-8be1-e860ec299999\":{\n" + + " \"id\":\"3x3tsh58-e6db-4dsa-8be1-e860ec299999\",\n" + + " \"label\":\"Nairobi\",\n" + + " \"node\":{\n" + + " \"locationId\":\"3x3tsh58-e6db-4dsa-8be1-e860ec299999\",\n" + + " \"name\":\"Nairobi\",\n" + + " \"parentLocation\":{\n" + + " \"locationId\":\"583j4bve4-9a36-459a-99fc-bc0356d99999\",\n" + + " \"voided\":false\n" + + " },\n" + + " \"tags\":[\n" + + " \"Province\"\n" + + " ],\n" + + " \"voided\":false\n" + + " },\n" + + " \"children\":{\n" + + " \"93437b43-485d-44df-8eaf-434449579999\":{\n" + + " \"id\":\"93437b43-485d-44df-8eaf-434449579999\",\n" + + " \"label\":\"Kasarani\",\n" + + " \"node\":{\n" + + " \"locationId\":\"93437b43-485d-44df-8eaf-434449579999\",\n" + + " \"name\":\"Kasarani\",\n" + + " \"parentLocation\":{\n" + + " \"locationId\":\"3x3tsh58-e6db-4dsa-8be1-e860ec299999\",\n" + + " \"voided\":false\n" + + " },\n" + + " \"tags\":[\n" + + " \"District\"\n" + + " ],\n" + + " \"voided\":false\n" + + " },\n" + + " \"children\":{\n" + + " \"9ue62k3a-88f3-886f-b990-bb529b399999\":{\n" + + " \"id\":\"9ue62k3a-88f3-886f-b990-bb529b399999\",\n" + + " \"label\":\"Roysambu\",\n" + + " \"node\":{\n" + + " \"locationId\":\"9ue62k3a-88f3-886f-b990-bb529b399999\",\n" + + " \"name\":\"Roysambu\",\n" + + " \"parentLocation\":{\n" + + " \"locationId\":\"93437b43-485d-44df-8eaf-434449579999\",\n" + + " \"voided\":false\n" + + " },\n" + + " \"tags\":[\n" + + " \"Ward\"\n" + + " ],\n" + + " \"voided\":false\n" + + " },\n" + + " \"children\":{\n" + + " \"kdjafls993-33d1-4322-9342-062j8cf99999\":{\n" + + " \"id\":\"kdjafls993-33d1-4322-9342-062j8cf99999\",\n" + + " \"label\":\"Thika Road Health Center\",\n" + + " \"node\":{\n" + + " \"locationId\":\"kdjafls993-33d1-4322-9342-062j8cf99999\",\n" + + " \"name\":\"Thika Road Health Center\",\n" + + " \"parentLocation\":{\n" + + " \"locationId\":\"9ue62k3a-88f3-886f-b990-bb529b399999\",\n" + + " \"voided\":false\n" + + " },\n" + + " \"tags\":[\n" + + " \"Facility\"\n" + + " ],\n" + + " \"voided\":false\n" + + " },\n" + + " \"parent\":\"9ue62k3a-88f3-886f-b990-bb529b399999\"\n" + + " }\n" + + " },\n" + + " \"parent\":\"93437b43-485d-44df-8eaf-434449579999\"\n" + + " }\n" + + " },\n" + + " \"parent\":\"3x3tsh58-e6db-4dsa-8be1-e860ec299999\"\n" + + " }\n" + + " },\n" + + " \"parent\":\"583j4bve4-9a36-459a-99fc-bc0356d99999\"\n" + + " }\n" + + " }\n" + + " }\n" + + " },\n" + + " \"parentChildren\":{\n" + + " \"9ue62k3a-88f3-886f-b990-bb529b399999\":[\n" + + " \"kdjafls993-33d1-4322-9342-062j8cf99999\"\n" + + " ],\n" + + " \"93437b43-485d-44df-8eaf-434449579999\":[\n" + + " \"9ue62k3a-88f3-886f-b990-bb529b399999\"\n" + + " ],\n" + + " \"583j4bve4-9a36-459a-99fc-bc0356d99999\":[\n" + + " \"3x3tsh58-e6db-4dsa-8be1-e860ec299999\"\n" + + " ],\n" + + " \"3x3tsh58-e6db-4dsa-8be1-e860ec299999\":[\n" + + " \"93437b43-485d-44df-8eaf-434449579999\"\n" + + " ]\n" + + " }\n" + + " }\n" + + " },\n" + + " \"team\":{\n" + + " \"identifier\":\"93c6526-6667-3333-a611112-f3b309999999\",\n" + + " \"locations\":[\n" + + " {\n" + + " \"display\":\"Health Team Kasarani\",\n" + + " \"name\":\"Health Team Kasarani\",\n" + + " \"uuid\":\"kdjafls993-33d1-4322-9342-062j8cf99999\"\n" + + " }\n" + + " ],\n" + + " \"team\":{\n" + + " \"teamName\":\"Health Team Kasarani\",\n" + + " \"organizationIds\":[\n" + + " 1.0\n" + + " ],\n" + + " \"display\":\"Health Team Kasarani\",\n" + + " \"location\":{\n" + + " \"display\":\"Health Team Kasarani\",\n" + + " \"name\":\"Health Team Kasarani\",\n" + + " \"uuid\":\"kdjafls993-33d1-4322-9342-062j8cf99999\"\n" + + " },\n" + + " \"uuid\":\"abf1be43-32da-4848-9b50-630fb89ec0ef\"\n" + + " },\n" + + " \"uuid\":\"93c6526-6667-3333-a611112-f3b309999999\"\n" + + " },\n" + + " \"time\":{\n" + + " \"time\":\"2020-06-02 08:21:40\",\n" + + " \"timeZone\":\"Africa/Nairobi\"\n" + + " },\n" + + " \"user\":{\n" + + " \"username\":\"demo\",\n" + + " \"roles\":[\n" + + " \"ROLE_OPENMRS\",\n" + + " \"ROLE_ALL_EVENTS\",\n" + + " \"ROLE_PLANS_FOR_USER\",\n" + + " \"ROLE_offline_access\",\n" + + " \"ROLE_uma_authorization\"\n" + + " ],\n" + + " \"permissions\":[\n" + + " \"ROLE_OPENMRS\",\n" + + " \"ROLE_ALL_EVENTS\",\n" + + " \"ROLE_PLANS_FOR_USER\",\n" + + " \"ROLE_offline_access\",\n" + + " \"ROLE_uma_authorization\"\n" + + " ],\n" + + " \"preferredName\":\"Demo User\",\n" + + " \"baseEntityId\":\"93c6526-6667-3333-a611112-f3b309999999\",\n" + + " \"attributes\":{\n" + + "\n" + + " },\n" + + " \"voided\":false\n" + + " },\n" + + " \"jurisdictions\":[\n" + + " \"Health Team Kasarani\"\n" + + " ]\n" + + "}"; +} From f91d166537991efd386cc9d5a64272f181f2e25f Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 2 Jun 2020 19:08:56 +0300 Subject: [PATCH 18/70] CI Configuration updates - Add codacy configuration file - Rename test case --- .codacy.yml | 6 ++++++ .../test/java/org/smartregister/service/HTTPAgentTest.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .codacy.yml diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 000000000..367d4457b --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,6 @@ +engines: + duplication: + exclude_paths: + - '**/test/**' + - '**/androidTest/**' + diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index c76839219..c433d2044 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -852,7 +852,7 @@ public void testUrlCanBeAccessWithGivenCredentialsReturnsCorrectErrorResponseFor } @Test - public void testUrlCanBeAccessWithGivenCredentialsReturnsCorrectErrorResponseErrorResponseForIOException() throws Exception { + public void testUrlCanBeAccessWithGivenCredentialsReturnsCorrectErrorResponseForIOException() throws Exception { PowerMockito.mockStatic(Base64.class); From 63a7ca66f80253b5fd8157489b551f45f466f1d9 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Wed, 3 Jun 2020 12:21:46 +0300 Subject: [PATCH 19/70] Fixed/Implment code review change requests on HTTP Agent test - Renamed test case - Refactored test case logic --- .../org/smartregister/service/HTTPAgentTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index c433d2044..8f0ae1e23 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -655,7 +655,7 @@ public void testFetchUserDetailsConstructsCorrectResponseForRandomServerError() Mockito.doReturn(httpsURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); - Mockito.doReturn(errorStream).when(httpsURLConnection).getInputStream(); + Mockito.doReturn(errorStream).when(httpsURLConnection).getErrorStream(); Mockito.doReturn(HttpURLConnection.HTTP_INTERNAL_ERROR).when(httpsURLConnection).getResponseCode(); PowerMockito.mockStatic(IOUtils.class); @@ -665,10 +665,10 @@ public void testFetchUserDetailsConstructsCorrectResponseForRandomServerError() Assert.assertNotNull(loginResponse); Assert.assertNotNull(loginResponse.message()); - Assert.assertEquals("Dristhi login failed. Try later", loginResponse.message()); + Assert.assertEquals("Oops, something went wrong", loginResponse.message()); Assert.assertNull(loginResponse.payload()); - Assert.assertEquals("UNKNOWN_RESPONSE", loginResponse.name()); + Assert.assertEquals("CUSTOM_SERVER_RESPONSE", loginResponse.name()); } @@ -719,7 +719,7 @@ public void testFetchUserDetailsConstructsCorrectResponseForConnectionTimedOutRe @Test - public void testFetchUserDetailsConstructsCorrectResponseForRequestsWithNetworkConnectivity() throws Exception { + public void testFetchUserDetailsConstructsCorrectResponseForRequestsWithoutNetworkConnectivity() throws Exception { URL url = PowerMockito.mock(URL.class); Assert.assertNotNull(url); @@ -840,14 +840,14 @@ public void testUrlCanBeAccessWithGivenCredentialsReturnsCorrectErrorResponseFor Mockito.doReturn(TEST_USERNAME).when(allSharedPreferences).fetchRegisteredANM(); Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(USER_DETAILS_ENDPOINT); - Mockito.doThrow(new MalformedURLException()).when(httpURLConnection).getResponseCode(); + Mockito.doThrow(new SocketTimeoutException()).when(httpURLConnection).getResponseCode(); LoginResponse response = httpAgentSpy.urlCanBeAccessWithGivenCredentials(USER_DETAILS_ENDPOINT, TEST_USERNAME, TEST_PASSWORD); Assert.assertNotNull(response); Assert.assertNull(response.payload()); Assert.assertNotNull(response.message()); - Assert.assertEquals(LoginResponse.MALFORMED_URL.name(), response.name()); + Assert.assertEquals(LoginResponse.TIMEOUT.name(), response.name()); } From fa29df3ea93cae8f064df26d146cf3c13e6a8dc0 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Wed, 3 Jun 2020 15:39:00 +0300 Subject: [PATCH 20/70] Remove unused imports --- .../main/java/org/smartregister/sample/SampleLoginActivity.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sample/src/main/java/org/smartregister/sample/SampleLoginActivity.java b/sample/src/main/java/org/smartregister/sample/SampleLoginActivity.java index 89f4de02c..8fde0248b 100644 --- a/sample/src/main/java/org/smartregister/sample/SampleLoginActivity.java +++ b/sample/src/main/java/org/smartregister/sample/SampleLoginActivity.java @@ -2,7 +2,6 @@ import android.content.Intent; import android.os.Bundle; -import android.widget.TextView; import org.smartregister.sample.presenter.LoginPresenter; import org.smartregister.view.activity.BaseLoginActivity; From a9e592cc63b1aa1489dd445e11e9b8d457849ccb Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Wed, 3 Jun 2020 15:41:52 +0300 Subject: [PATCH 21/70] Refactor to remove duplicate code : DRY --- .../org/smartregister/util/OpenSRPImageLoader.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/util/OpenSRPImageLoader.java b/opensrp-app/src/main/java/org/smartregister/util/OpenSRPImageLoader.java index 2ebede8e3..4a56c87e9 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/OpenSRPImageLoader.java +++ b/opensrp-app/src/main/java/org/smartregister/util/OpenSRPImageLoader.java @@ -160,8 +160,7 @@ private static RequestQueue newRequestQueue(Context context) { public HttpResponse performRequest(Request request, Map headers) throws IOException, AuthFailureError { - String accessToken = AccountHelper.getOAuthToken(CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); - headers.put(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, new StringBuilder(AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " ").append(accessToken).toString()); + addBearerTokenAuthorizationHeader(headers); return super.performRequest(request, headers); } @@ -175,8 +174,7 @@ public HttpResponse performRequest(Request request, Map @Override public HttpResponse performRequest(Request request, Map headers) throws IOException, AuthFailureError { - String accessToken = AccountHelper.getOAuthToken(CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); - headers.put(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, new StringBuilder(AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " ").append(accessToken).toString()); + addBearerTokenAuthorizationHeader(headers); return super.performRequest(request, headers); } @@ -187,6 +185,11 @@ public HttpResponse performRequest(Request request, Map heade return requestQueue; } + private static void addBearerTokenAuthorizationHeader(Map headers) { + String accessToken = AccountHelper.getOAuthToken(CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + headers.put(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, new StringBuilder(AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " ").append(accessToken).toString()); + } + /** * Sets a {@link Bitmap} to an {@link ImageView} using a fade-in animation. If there is a * {@link Drawable} already set on the ImageView then use that as the image to fade from. From 599b4c297881030dad9f38a6f29c13bcc95f8bc9 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Fri, 5 Jun 2020 20:19:19 +0300 Subject: [PATCH 22/70] Add unit tests - Add unit tests for HTTPAgent.downloadFormUrl --- .../org/smartregister/service/HTTPAgent.java | 59 +++++++--- .../smartregister/service/HTTPAgentTest.java | 109 ++++++++++++++++++ 2 files changed, 153 insertions(+), 15 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index 70d5aad10..f69121a4a 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -4,7 +4,6 @@ import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.util.Base64; -import android.util.Log; import com.google.common.io.BaseEncoding; import com.google.gson.Gson; @@ -40,6 +39,7 @@ import java.io.Closeable; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -426,7 +426,7 @@ public String httpImagePost(String urlString, ProfileImage image) { } private void attachImage(PrintWriter writer, ProfileImage image, OutputStream outputStream) throws IOException { - File uploadImageFile = new File(image.getFilepath()); + File uploadImageFile = getDownloadFolder(image.getFilepath()); String fileName = uploadImageFile.getName(); writer.append("--" + boundary).append(crlf); @@ -722,18 +722,18 @@ public Response downloadFromURL(String downloadURL_, String file HttpURLConnection httpUrlConnection = null; try { - File dir = new File(FormPathService.sdcardPathDownload); + File dir = getSDCardDownloadPath(); if (!dir.exists()) { dir.mkdirs(); } - File file = new File(dir, fileName); + File file = getFile(fileName, dir); long startTime = System.currentTimeMillis(); - Log.d("DownloadFormService", "download begin"); - Log.d("DownloadFormService", "download url: " + downloadURL_); - Log.d("DownloadFormService", "download file name: " + fileName); + Timber.d("DownloadFormService %s", "download begin"); + Timber.d("DownloadFormService %s %s", "download url: ", downloadURL_); + Timber.d("DownloadFormService %s %s", "download file name: ", fileName); String downloadURL = downloadURL_.replaceAll("\\s+", ""); @@ -744,14 +744,14 @@ public Response downloadFromURL(String downloadURL_, String file if (status == HttpURLConnection.HTTP_OK) { InputStream inputStream = httpUrlConnection.getInputStream(); - BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); + BufferedInputStream bufferedInputStream = getBufferedInputStream(inputStream); long fileLength = bufferedInputStream.available(); if (fileLength == 0) { return new Response(ResponseStatus.success, DownloadStatus.nothingDownloaded); } - Log.d("DownloadFormService", "file length : " + fileLength); + Timber.d("DownloadFormService %s %d", "file length : ", fileLength); ByteArrayBuffer baf = new ByteArrayBuffer(9999); int current = 0; @@ -760,22 +760,22 @@ public Response downloadFromURL(String downloadURL_, String file } /* Convert the bytes to String */ - FileOutputStream fos = new FileOutputStream(file); + FileOutputStream fos = getFileOutputStream(file); fos.write(baf.toByteArray()); fos.flush(); fos.close(); - Log.d("DownloadFormService", - "download finished in " + ((System.currentTimeMillis() - startTime) / 1000) - + " sec"); + Timber.d("DownloadFormService %s %d %s", + "download finished in ", ((System.currentTimeMillis() - startTime) / 1000) + , " sec"); } else { - Log.d("RESPONSE", "Server returned non-OK status: " + status); + Timber.d("RESPONSE %s %s ", "Server returned non-OK status: ", status); return new Response(ResponseStatus.failure, DownloadStatus.failedDownloaded); } } catch (IOException e) { - Log.d("DownloadFormService", "download error : " + e); + Timber.d(e, "DownloadFormService"); return new Response(ResponseStatus.success, DownloadStatus.failedDownloaded); } finally { @@ -785,6 +785,35 @@ public Response downloadFromURL(String downloadURL_, String file return new Response(ResponseStatus.success, DownloadStatus.downloaded); } + @VisibleForTesting + @NonNull + protected FileOutputStream getFileOutputStream(File file) throws FileNotFoundException { + return new FileOutputStream(file); + } + + @VisibleForTesting + @NonNull + protected BufferedInputStream getBufferedInputStream(InputStream inputStream) { + return new BufferedInputStream(inputStream); + } + + @VisibleForTesting + @NonNull + protected File getFile(String fileName, File dir) { + return new File(dir, fileName); + } + + @VisibleForTesting + protected File getSDCardDownloadPath() { + return getDownloadFolder(FormPathService.sdcardPathDownload); + } + + @NonNull + @VisibleForTesting + protected File getDownloadFolder(String sdcardPathDownload) { + return new File(sdcardPathDownload); + } + public boolean verifyAuthorization() { String baseUrl = configuration.dristhiBaseURL(); diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index 8f0ae1e23..be37a2701 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -32,6 +32,7 @@ import org.smartregister.account.AccountConfiguration; import org.smartregister.account.AccountHelper; import org.smartregister.account.AccountResponse; +import org.smartregister.domain.DownloadStatus; import org.smartregister.domain.LoginResponse; import org.smartregister.domain.ProfileImage; import org.smartregister.domain.Response; @@ -39,8 +40,10 @@ import org.smartregister.repository.AllSharedPreferences; import org.smartregister.util.LoginResponseTestData; +import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -104,6 +107,18 @@ public class HTTPAgentTest { @Mock private InputStream errorStream; + @Mock + private File dirFile; + + @Mock + private File file; + + @Mock + private BufferedInputStream bufferedInputStream; + + @Mock + private FileOutputStream fileOutputStream; + @Rule private TemporaryFolder folder = new TemporaryFolder(); @@ -115,6 +130,7 @@ public class HTTPAgentTest { private static final String SECURE_RESOURCE_ENDPOINT = "https://my-server.com/my/secure/resource"; private static final String KEYClOAK_CONFIGURATION_ENDPOINT = "https://my-server.com/rest/config/keycloak"; private static final String USER_DETAILS_ENDPOINT = "https://my-server.com/opensrp/security/authenticate"; + private static final String TEST_IMAGE_DOWNLOAD_ENDPOINT = "https://my-server.com/opensrp/multimedia/myimage.jpg"; private final String SAMPLE_TEST_TOKEN = "sample-test-token"; private final String SAMPLE_REFRESH_TOKEN = "sample-refresh-token"; @@ -126,6 +142,7 @@ public class HTTPAgentTest { private static final String OAUTH_CONFIGURATION_SERVER_RESPONSE = "{\"issuer\":\"https://my-server.com/oauth/issuer\",\r\n\"authorization_endpoint\": \"https://my-server.com/oauth/auth\",\r\n\"token_endpoint\": \"https://my-server.com/oauth/token\",\r\n\"grant_types_supported\":[\"authorization code\",\"implicit\",\"password\"]\r\n}"; private static final String FETCH_DATA_REQUEST_SERVER_RESPONSE = "{status:{\"response_status\":\"success\"},payload: \"My secure resources from the server\"\r\n\r\n}"; private static final String SAMPLE_POST_REQUEST_PAYLOAD = "{\"payload\":\"My POST Payload\"}"; + private static final String TEST_FILE_NAME = "Profile"; @Before public void setUp() { @@ -904,4 +921,96 @@ public void testUrlCanBeAccessWithGivenCredentialsReturnsCorrectResponseForRando Assert.assertEquals("CUSTOM_SERVER_RESPONSE", loginResponse.name()); } + + @Test + public void testDownloadFromUrl() throws Exception { + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(dirFile).when(httpAgentSpy).getSDCardDownloadPath(); + Mockito.doReturn(file).when(httpAgentSpy).getFile(TEST_FILE_NAME, dirFile); + Mockito.doReturn(false).when(dirFile).exists(); + Mockito.doReturn(true).when(dirFile).mkdirs(); + + Mockito.doReturn(httpsURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_IMAGE_DOWNLOAD_ENDPOINT); + Mockito.doReturn(HttpURLConnection.HTTP_OK).when(httpsURLConnection).getResponseCode(); + Mockito.doReturn(inputStream).when(httpsURLConnection).getInputStream(); + Mockito.doReturn(bufferedInputStream).when(httpAgentSpy).getBufferedInputStream(inputStream); + Mockito.doReturn(1985).when(bufferedInputStream).available(); + Mockito.doReturn(-1).when(bufferedInputStream).read(); + + Mockito.doReturn(fileOutputStream).when(httpAgentSpy).getFileOutputStream(file); + + DownloadStatus downloadStatus = httpAgentSpy.downloadFromUrl(TEST_IMAGE_DOWNLOAD_ENDPOINT, TEST_FILE_NAME); + Assert.assertNotNull(downloadStatus); + Assert.assertEquals("Download successful", downloadStatus.displayValue()); + + Mockito.verify(fileOutputStream).write(ArgumentMatchers.any(byte[].class)); + Mockito.verify(fileOutputStream).flush(); + Mockito.verify(fileOutputStream).close(); + + } + + + @Test + public void testDownloadFromUrlReturnsCorrectResponseIfNothingDownloaded() throws Exception { + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(dirFile).when(httpAgentSpy).getSDCardDownloadPath(); + Mockito.doReturn(file).when(httpAgentSpy).getFile(TEST_FILE_NAME, dirFile); + Mockito.doReturn(false).when(dirFile).exists(); + Mockito.doReturn(true).when(dirFile).mkdirs(); + + Mockito.doReturn(httpsURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_IMAGE_DOWNLOAD_ENDPOINT); + Mockito.doReturn(HttpURLConnection.HTTP_OK).when(httpsURLConnection).getResponseCode(); + + Mockito.doReturn(inputStream).when(httpsURLConnection).getInputStream(); + + DownloadStatus downloadStatus = httpAgentSpy.downloadFromUrl(TEST_IMAGE_DOWNLOAD_ENDPOINT, TEST_FILE_NAME); + Assert.assertNotNull(downloadStatus); + Assert.assertEquals("Nothing downloaded.", downloadStatus.displayValue()); + + + } + + @Test + public void testDownloadFromUrlReturnsCorrectResponseIfIOExceptionThrown() throws Exception { + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(dirFile).when(httpAgentSpy).getSDCardDownloadPath(); + Mockito.doReturn(file).when(httpAgentSpy).getFile(TEST_FILE_NAME, dirFile); + Mockito.doReturn(false).when(dirFile).exists(); + Mockito.doReturn(true).when(dirFile).mkdirs(); + + Mockito.doReturn(httpsURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_IMAGE_DOWNLOAD_ENDPOINT); + Mockito.doThrow(new IOException()).when(httpsURLConnection).getResponseCode(); + + DownloadStatus downloadStatus = httpAgentSpy.downloadFromUrl(TEST_IMAGE_DOWNLOAD_ENDPOINT, TEST_FILE_NAME); + Assert.assertNotNull(downloadStatus); + Assert.assertEquals("Download failed.", downloadStatus.displayValue()); + + + } + + @Test + public void testDownloadFromUrlReturnsCorrectResponseIfConnectionStatusIsNOT200() throws Exception { + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(dirFile).when(httpAgentSpy).getSDCardDownloadPath(); + Mockito.doReturn(file).when(httpAgentSpy).getFile(TEST_FILE_NAME, dirFile); + Mockito.doReturn(true).when(dirFile).exists(); + + Mockito.doReturn(httpsURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_IMAGE_DOWNLOAD_ENDPOINT); + Mockito.doReturn(HttpURLConnection.HTTP_NOT_FOUND).when(httpsURLConnection).getResponseCode(); + + DownloadStatus downloadStatus = httpAgentSpy.downloadFromUrl(TEST_IMAGE_DOWNLOAD_ENDPOINT, TEST_FILE_NAME); + Assert.assertNotNull(downloadStatus); + Assert.assertEquals("Download failed.", downloadStatus.displayValue()); + + + } + } From c9c4a0df78c261f17a269aa253bb48cb43e18d95 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Mon, 8 Jun 2020 22:37:29 +0300 Subject: [PATCH 23/70] Refactor client core : Remove password type String - Remove type String for password processing to mitigates against the heap dump attack vector exposed by Java String type - Fix unit tests Signed-off-by: Martin Ndegwa --- .../smartregister/util/FakeUserService.java | 14 +-- .../login/interactor/BaseLoginInteractor.java | 22 ++-- .../login/model/BaseLoginModel.java | 6 +- .../login/presenter/BaseLoginPresenter.java | 4 +- .../login/task/RemoteLoginTask.java | 4 +- .../repository/AllSharedPreferences.java | 14 --- .../smartregister/repository/Repository.java | 6 +- .../security/SecurityHelper.java | 111 ++++++++++++++++++ .../org/smartregister/service/HTTPAgent.java | 22 ++-- .../smartregister/service/UserService.java | 103 ++++++++-------- .../java/org/smartregister/util/Session.java | 8 +- .../view/activity/BaseLoginActivity.java | 5 +- .../view/activity/DrishtiApplication.java | 6 +- .../view/activity/LoginActivity.java | 21 ++-- .../view/contract/BaseLoginContract.java | 6 +- .../org/smartregister/TestApplication.java | 4 +- .../presenter/BaseLoginPresenterTest.java | 3 +- .../repository/AllSharedPreferencesTest.java | 12 -- .../repository/mock/RepositoryMock.java | 2 +- .../smartregister/service/HTTPAgentTest.java | 12 +- .../service/UserServiceTest.java | 22 ++-- .../view/activity/LoginActivityTest.java | 4 +- .../LoginActivityWithRemoteLoginTest.java | 6 +- 23 files changed, 259 insertions(+), 158 deletions(-) create mode 100644 opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java diff --git a/opensrp-app/src/androidTest/java/org/smartregister/util/FakeUserService.java b/opensrp-app/src/androidTest/java/org/smartregister/util/FakeUserService.java index 21f03d6cd..7a229ea35 100644 --- a/opensrp-app/src/androidTest/java/org/smartregister/util/FakeUserService.java +++ b/opensrp-app/src/androidTest/java/org/smartregister/util/FakeUserService.java @@ -22,29 +22,29 @@ public FakeUserService() { } @Override - public boolean isValidLocalLogin(String userName, String password) { + public boolean isValidLocalLogin(String userName, char[] password) { assertExpectedCredentials(userName, password); actualCalls.add("local"); return shouldSucceedLocalLogin; } @Override - public LoginResponse isValidRemoteLogin(String userName, String password) { + public LoginResponse isValidRemoteLogin(String userName, char[] password) { assertExpectedCredentials(userName, password); actualCalls.add("remote"); return shouldSucceedRemoteLogin; } @Override - public void localLogin(String userName, String password) { - super.setupContextForLogin(userName, password); + public void localLogin(String userName, char[] password) { + super.setupContextForLogin(password); actualCalls.add("login"); assertExpectedCredentials(userName, password); } - public void remoteLogin(String userName, String password, String anmLocation) { - super.setupContextForLogin(userName, password); + public void remoteLogin(String userName, char[] password, String anmLocation) { + super.setupContextForLogin(password); actualCalls.add("login"); assertExpectedCredentials(userName, password); } @@ -65,7 +65,7 @@ public void logoutSession() { super.logoutSession(); } - private void assertExpectedCredentials(String userName, String password) { + private void assertExpectedCredentials(String userName, char[] password) { if (!expectedUserName.equals(userName)) { throw new RuntimeException("Expected user: " + expectedUserName + ". Actual: " + userName); } diff --git a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java index fbca7463e..4c64006eb 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java +++ b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java @@ -61,12 +61,12 @@ public void onDestroy(boolean isChangingConfiguration) { } @Override - public void login(WeakReference view, String userName, String password) { + public void login(WeakReference view, String userName, char[] password) { loginWithLocalFlag(view, !getSharedPreferences().fetchForceRemoteLogin() && userName.equalsIgnoreCase(getSharedPreferences().fetchRegisteredANM()), userName, password); } - public void loginWithLocalFlag(WeakReference view, boolean localLogin, String userName, String password) { + public void loginWithLocalFlag(WeakReference view, boolean localLogin, String userName, char[] password) { getLoginView().hideKeyboard(); getLoginView().enableLoginButton(false); @@ -79,7 +79,7 @@ public void loginWithLocalFlag(WeakReference view, boole Timber.i("Login result finished " + DateTime.now().toString()); } - private void localLogin(WeakReference view, String userName, String password) { + private void localLogin(WeakReference view, String userName, char[] password) { getLoginView().enableLoginButton(true); boolean isAuthenticated = getUserService().isUserInValidGroup(userName, password); if (!isAuthenticated) { @@ -88,16 +88,16 @@ private void localLogin(WeakReference view, String userN } else if (isAuthenticated && (!AllConstants.TIME_CHECK || TimeStatus.OK.equals(getUserService().validateStoredServerTimeZone()))) { - navigateToHomePage(userName); + navigateToHomePage(userName, password); } else { loginWithLocalFlag(view, false, userName, password); } } - private void navigateToHomePage(String userName) { + private void navigateToHomePage(String userName, char[] password) { - getUserService().localLogin(userName); + getUserService().localLogin(userName, password); getLoginView().goToHome(false); CoreLibrary.getInstance().initP2pLibrary(userName); @@ -116,7 +116,7 @@ public void run() { }).start(); } - private void remoteLogin(final String userName, final String password, final AccountAuthenticatorXml accountAuthenticatorXml) { + private void remoteLogin(final String userName, final char[] password, final AccountAuthenticatorXml accountAuthenticatorXml) { try { if (getSharedPreferences().fetchBaseURL("").isEmpty() && StringUtils.isNotBlank(this.getApplicationContext().getString(R.string.opensrp_url))) { @@ -136,7 +136,7 @@ public void onEvent(LoginResponse loginResponse) { ); if (!AllConstants.TIME_CHECK || timeStatus.equals(TimeStatus.OK)) { - remoteLoginWith(username, loginResponse); + remoteLoginWith(username, password, loginResponse); } else { if (timeStatus.equals(TimeStatus.TIMEZONE_MISMATCH)) { @@ -199,7 +199,7 @@ public void onComplete() { } } - private void tryRemoteLogin(final String userName, final String password, final AccountAuthenticatorXml accountAuthenticatorXml, final Listener afterLogincheck) { + private void tryRemoteLogin(final String userName, final char[] password, final AccountAuthenticatorXml accountAuthenticatorXml, final Listener afterLogincheck) { if (remoteLoginTask != null && !remoteLoginTask.isCancelled()) { remoteLoginTask.cancel(true); } @@ -207,8 +207,8 @@ private void tryRemoteLogin(final String userName, final String password, final remoteLoginTask.execute(); } - private void remoteLoginWith(String userName, LoginResponse loginResponse) { - getUserService().processLoginResponseDataForUser(userName, loginResponse.payload()); + private void remoteLoginWith(String userName, char[] password, LoginResponse loginResponse) { + getUserService().processLoginResponseDataForUser(userName, password, loginResponse.payload()); processServerSettings(loginResponse); scheduleJobsPeriodically(); diff --git a/opensrp-app/src/main/java/org/smartregister/login/model/BaseLoginModel.java b/opensrp-app/src/main/java/org/smartregister/login/model/BaseLoginModel.java index 8047a5237..fc9f6a345 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/model/BaseLoginModel.java +++ b/opensrp-app/src/main/java/org/smartregister/login/model/BaseLoginModel.java @@ -12,13 +12,13 @@ public class BaseLoginModel implements BaseLoginContract.Model { @Override public org.smartregister.Context getOpenSRPContext() { - return CoreLibrary.getInstance().context(); + return CoreLibrary.getInstance().context(); } @Override - public boolean isPasswordValid(String password) { - return !TextUtils.isEmpty(password) && password.length() > 1; + public boolean isPasswordValid(char[] password) { + return password != null && password.length > 1; } @Override diff --git a/opensrp-app/src/main/java/org/smartregister/login/presenter/BaseLoginPresenter.java b/opensrp-app/src/main/java/org/smartregister/login/presenter/BaseLoginPresenter.java index b1b4d246d..9dd86e6e9 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/presenter/BaseLoginPresenter.java +++ b/opensrp-app/src/main/java/org/smartregister/login/presenter/BaseLoginPresenter.java @@ -42,7 +42,7 @@ public void onDestroy(boolean isChangingConfiguration) { } @Override - public void attemptLogin(String username, String password) { + public void attemptLogin(String username, char[] password) { if (!mLoginView.get().isAppVersionAllowed()) { getLoginView().showErrorDialog(getLoginView() .getActivityContext().getResources().getString(R.string.outdated_app)); @@ -69,7 +69,7 @@ public void attemptLogin(String username, String password) { } if (!cancel) { - mLoginInteractor.login(mLoginView, username.trim(), password.trim()); + mLoginInteractor.login(mLoginView, username.trim(), password); } } diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index 6052d4b3e..280a181e9 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -37,12 +37,12 @@ public class RemoteLoginTask extends AsyncTask { private BaseLoginContract.View mLoginView; private final String mUsername; - private final String mPassword; + private final char[] mPassword; private final AccountAuthenticatorXml mAccountAuthenticatorXml; private final Listener afterLoginCheck; - public RemoteLoginTask(BaseLoginContract.View loginView, String username, String password, AccountAuthenticatorXml accountAuthenticatorXml, Listener afterLoginCheck) { + public RemoteLoginTask(BaseLoginContract.View loginView, String username, char[] password, AccountAuthenticatorXml accountAuthenticatorXml, Listener afterLoginCheck) { mLoginView = loginView; mUsername = username; mPassword = password; diff --git a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java index 8647aee7c..8c374f56c 100644 --- a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java @@ -15,7 +15,6 @@ import static org.smartregister.AllConstants.DEFAULT_TEAM_ID_PREFIX; import static org.smartregister.AllConstants.DEFAULT_TEAM_PREFIX; import static org.smartregister.AllConstants.DRISHTI_BASE_URL; -import static org.smartregister.AllConstants.ENCRYPTED_GROUP_ID_PREFIX; import static org.smartregister.AllConstants.FORCE_REMOTE_LOGIN; import static org.smartregister.AllConstants.IS_SYNC_INITIAL_KEY; import static org.smartregister.AllConstants.IS_SYNC_IN_PROGRESS_PREFERENCE_KEY; @@ -144,19 +143,6 @@ public void saveCurrentLocality(String currentLocality) { preferences.edit().putString(CURRENT_LOCALITY, currentLocality).commit(); } - public String fetchEncryptedGroupId(String username) { - if (username != null) { - return preferences.getString(ENCRYPTED_GROUP_ID_PREFIX + username, null); - } - return null; - } - - public void saveEncryptedGroupId(String username, String groupId) { - if (username != null) { - preferences.edit().putString(ENCRYPTED_GROUP_ID_PREFIX + username, groupId).commit(); - } - } - public String fetchLanguagePreference() { return preferences.getString(LANGUAGE_PREFERENCE_KEY, DEFAULT_LOCALE).trim(); } diff --git a/opensrp-app/src/main/java/org/smartregister/repository/Repository.java b/opensrp-app/src/main/java/org/smartregister/repository/Repository.java index f1b5c1b6a..81c4d32b4 100755 --- a/opensrp-app/src/main/java/org/smartregister/repository/Repository.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/Repository.java @@ -156,7 +156,7 @@ public SQLiteDatabase getWritableDatabase() { return getWritableDatabase(password()); } - private boolean isDatabaseWritable(String password) { + private boolean isDatabaseWritable(char[] password) { SQLiteDatabase database = SQLiteDatabase .openDatabase(databasePath.getPath(), password, null, SQLiteDatabase.OPEN_READONLY, hook); @@ -164,7 +164,7 @@ private boolean isDatabaseWritable(String password) { return true; } - public boolean canUseThisPassword(String password) { + public boolean canUseThisPassword(char[] password) { try { return isDatabaseWritable(password); } catch (SQLiteException e) { @@ -190,7 +190,7 @@ public boolean canUseThisPassword(String password) { } } - private String password() { + private char[] password() { return DrishtiApplication.getInstance().getPassword(); } diff --git a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java new file mode 100644 index 000000000..91b4f3d7a --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java @@ -0,0 +1,111 @@ +package org.smartregister.security; + +import android.text.Editable; + +import org.apache.commons.codec.CharEncoding; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.util.Arrays; + +/** + * Created by ndegwamartin on 04/06/2020. + */ +public class SecurityHelper { + + private static Charset charset = Charset.forName(CharEncoding.UTF_8); + + /** + * This method ensures that sensitive info can be collected for the edit text in a safer way + */ + public static char[] readValue(Editable editable) { + + char[] chars = new char[editable.length()]; + editable.getChars(0, editable.length(), chars, 0); + + editable.clear(); + + return chars; + } + + /** + * This method allows us to overwrite byte array data + * + * @param array character array + */ + public static void clearArray(byte[] array) { + + Arrays.fill(array, (byte) 0); + } + + /** + * This method allows us to overwrite byte array data thus removing original values from memory + * + * @param array character array + */ + public static void clearArray(char[] array) { + Arrays.fill(array, '*'); + } + + /** + * This method converts characters in the string buffer to byte array without creating a String object + * + * @param stringBuffer + * @return an array of bytes , a conversion from the string buffer + */ + + public static byte[] toBytes(StringBuffer stringBuffer) throws CharacterCodingException { + + CharsetEncoder encoder = charset.newEncoder(); + + CharBuffer buffer = CharBuffer.wrap(stringBuffer); + + ByteBuffer bytesBuffer = encoder.encode(buffer); + + byte[] bytes = bytesBuffer.array(); + + clearArray(bytesBuffer.array()); + + clearStringBuffer(stringBuffer); + + return bytes; + } + + private static void clearStringBuffer(StringBuffer stringBuffer) { + stringBuffer.setLength(0); + stringBuffer.append("*"); + } + + /** + * This method converts characters in the string buffer to byte array without creating a String object + * + * @param chars array + * @return an array of bytes , a conversion from the chars array + */ + public static byte[] toBytes(char[] chars) { + CharBuffer charBuffer = CharBuffer.wrap(chars); + + ByteBuffer byteBuffer = charset.encode(charBuffer); + + byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()); + + clearArray(byteBuffer.array()); + + return bytes; + + } + + public static char[] toChars(byte[] bytes) { + + char[] convertedChar = new char[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + convertedChar[i] = (char) bytes[i]; + } + + return convertedChar; + + } +} diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index f69121a4a..9a9e962bd 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -30,6 +30,7 @@ import org.smartregister.domain.ResponseStatus; import org.smartregister.domain.jsonmapping.LoginResponseData; import org.smartregister.repository.AllSharedPreferences; +import org.smartregister.security.SecurityHelper; import org.smartregister.ssl.OpensrpSSLHelper; import org.smartregister.util.Utils; @@ -223,7 +224,7 @@ private void logResponse(String postURLPath, String jsonPayload) { Timber.d("postURLPath: %s and jsonPayLoad: %s", postURLPath, jsonPayload); } - public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, String userName, String password) { + public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, String userName, char[] password) { LoginResponse loginResponse = null; HttpURLConnection urlConnection = null; String url = null; @@ -231,7 +232,8 @@ public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, Strin url = requestURL.replaceAll("\\s+", ""); urlConnection = initializeHttp(url, false); - final String basicAuth = AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + Base64.encodeToString((userName + ":" + password).getBytes(), Base64.NO_WRAP); + byte[] credentials = SecurityHelper.toBytes(new StringBuffer(userName).append(':').append(password));//(new StringBuffer(userName).append(':').append(password)).toString().getBytes(); + final String basicAuth = AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + Base64.encodeToString(credentials, Base64.NO_WRAP); urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, basicAuth); int statusCode = urlConnection.getResponseCode(); InputStream inputStream; @@ -551,7 +553,7 @@ public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } - public AccountResponse oauth2authenticateCore(StringBuilder requestParamBuilder, String grantType, String tokenEndpointURL) { + public AccountResponse oauth2authenticateCore(StringBuffer requestParamBuffer, String grantType, String tokenEndpointURL) { AccountError accountError; @@ -568,11 +570,11 @@ public AccountResponse oauth2authenticateCore(StringBuilder requestParamBuilder, final String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes()); - requestParamBuilder.append("&grant_type=").append(grantType); - requestParamBuilder.append("&client_id=").append(clientId); - requestParamBuilder.append("&client_secret=").append(clientSecret); + requestParamBuffer.append("&grant_type=").append(grantType); + requestParamBuffer.append("&client_id=").append(clientId); + requestParamBuffer.append("&client_secret=").append(clientSecret); - byte[] postData = requestParamBuilder.toString().getBytes(CharEncoding.UTF_8); + byte[] postData = requestParamBuffer.toString().getBytes(CharEncoding.UTF_8); int postDataLength = postData.length; urlConnection.setFixedLengthStreamingMode(postDataLength); @@ -637,9 +639,9 @@ public AccountResponse oauth2authenticateCore(StringBuilder requestParamBuilder, } - public AccountResponse oauth2authenticate(String username, String password, String grantType, String tokenEndpointURL) { + public AccountResponse oauth2authenticate(String username, char[] password, String grantType, String tokenEndpointURL) { - StringBuilder requestParamBuilder = new StringBuilder(); + StringBuffer requestParamBuilder = new StringBuffer(); requestParamBuilder.append("&username=").append(username); requestParamBuilder.append("&password=").append(password); @@ -649,7 +651,7 @@ public AccountResponse oauth2authenticate(String username, String password, Stri public AccountResponse oauth2authenticateRefreshToken(String refreshToken) { String tokenEndpointURL = allSharedPreferences.getPreferences().getString(AccountHelper.CONFIGURATION_CONSTANTS.TOKEN_ENDPOINT_URL, ""); - StringBuilder requestParamBuilder = new StringBuilder(); + StringBuffer requestParamBuilder = new StringBuffer(); requestParamBuilder.append("&refresh_token=").append(refreshToken); return oauth2authenticateCore(requestParamBuilder, AccountHelper.OAUTH.GRANT_TYPE.REFRESH_TOKEN, tokenEndpointURL); diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 01b199547..e105de17f 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -24,6 +24,7 @@ import org.smartregister.domain.jsonmapping.util.TeamMember; import org.smartregister.repository.AllSettings; import org.smartregister.repository.AllSharedPreferences; +import org.smartregister.security.SecurityHelper; import org.smartregister.sync.SaveANMLocationTask; import org.smartregister.sync.SaveANMTeamTask; import org.smartregister.sync.SaveUserInfoTask; @@ -71,7 +72,6 @@ public class UserService { private static final String KEYSTORE = "AndroidKeyStore"; private static final String CIPHER = "RSA/ECB/PKCS1Padding"; private static final String CIPHER_PROVIDER = "AndroidOpenSSL"; - private static final String CIPHER_TEXT_CHARACTER_CODE = "UTF-8"; private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); @@ -207,19 +207,19 @@ public TimeStatus validateDeviceTime(LoginResponseData userInfo, long serverTime return TimeStatus.ERROR; } - public boolean isValidLocalLogin(String userName, String password) { + public boolean isValidLocalLogin(String userName, char[] password) { return allSharedPreferences.fetchRegisteredANM().equals(userName) && DrishtiApplication.getInstance().getRepository() .canUseThisPassword(password) && !allSharedPreferences.fetchForceRemoteLogin(); } - public boolean isUserInValidGroup(final String userName, final String password) { + public boolean isUserInValidGroup(final String userName, final char[] password) { // Check if everything OK for local login if (keyStore != null && userName != null && password != null && !allSharedPreferences.fetchForceRemoteLogin()) { String username = userName.equalsIgnoreCase(allSharedPreferences.fetchRegisteredANM()) ? allSharedPreferences.fetchRegisteredANM() : userName; try { KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(username); if (privateKeyEntry != null) { - String groupId = getGroupId(username, privateKeyEntry); + char[] groupId = getGroupId(username, privateKeyEntry); if (groupId != null) { return isValidGroupId(groupId); } @@ -232,11 +232,11 @@ public boolean isUserInValidGroup(final String userName, final String password) return false; } - private boolean isValidGroupId(String groupId) { + private boolean isValidGroupId(char[] groupId) { return DrishtiApplication.getInstance().getRepository().canUseThisPassword(groupId); } - public String getGroupId(String userName) { + public char[] getGroupId(String userName) { if (keyStore != null && userName != null) { try { KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); @@ -248,12 +248,14 @@ public String getGroupId(String userName) { return null; } - public String getGroupId(String userName, KeyStore.PrivateKeyEntry privateKeyEntry) { + public char[] getGroupId(String userName, KeyStore.PrivateKeyEntry privateKeyEntry) { if (privateKeyEntry != null) { - String encryptedGroupId = allSharedPreferences.fetchEncryptedGroupId(userName); + + String encryptedGroupId = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); + if (encryptedGroupId != null) { try { - return decryptString(privateKeyEntry, encryptedGroupId); + return SecurityHelper.toChars(decryptString(privateKeyEntry, encryptedGroupId)); } catch (Exception e) { Timber.e(e); } @@ -274,8 +276,8 @@ public boolean isUserInPioneerGroup(String userName) { if (userName.equals(pioneerUser)) { return true; } else { - String userGroupId = getGroupId(userName); - String pioneerGroupId = getGroupId(pioneerUser); + char[] userGroupId = getGroupId(userName); + char[] pioneerGroupId = getGroupId(pioneerUser); if (userGroupId != null && userGroupId.equals(pioneerGroupId)) { return isValidGroupId(userGroupId); @@ -295,7 +297,7 @@ public LoginResponse fetchUserDetails(String accessToken) { return loginResponse; } - public LoginResponse isValidRemoteLogin(String userName, String password) { + public LoginResponse isValidRemoteLogin(String userName, char[] password) { String requestURL; requestURL = configuration.dristhiBaseURL() + OPENSRP_AUTH_USER_URL_PATH; @@ -318,20 +320,27 @@ public Response getLocationInformation() { return httpAgent.fetch(requestURL); } - private boolean loginWith(String userName) { + private boolean loginWith(String userName, char[] password) { boolean loginSuccessful = true; - String encryptedGroupId = allSharedPreferences.fetchEncryptedGroupId(userName); - try { - KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); - if (privateKeyEntry != null) { - String groupId = decryptString(privateKeyEntry, encryptedGroupId); - setupContextForLogin(userName, groupId); + + if (usesGroupIdAsDBPassword(userName)) { + + String encryptedGroupId = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); + + try { + KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); + if (privateKeyEntry != null) { + byte[] groupId = Base64.decode(encryptedGroupId, Base64.DEFAULT); + setupContextForLogin(SecurityHelper.toChars(decryptString(privateKeyEntry, encryptedGroupId))); + SecurityHelper.clearArray(groupId); + } + } catch (Exception e) { + Timber.e(e); + loginSuccessful = false; } - } catch (Exception e) { - Timber.e(e); - loginSuccessful = false; + } else { + setupContextForLogin(password); } - String username = userName; if (allSharedPreferences.fetchRegisteredANM().equalsIgnoreCase(userName)) username = allSharedPreferences.fetchRegisteredANM(); @@ -350,7 +359,7 @@ private boolean loginWith(String userName) { private boolean usesGroupIdAsDBPassword(String userName) { try { if (keyStore != null && keyStore.containsAlias(userName)) { - if (allSharedPreferences.fetchEncryptedGroupId(userName) != null) { + if (AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()) != null) { return true; } } @@ -360,14 +369,14 @@ private boolean usesGroupIdAsDBPassword(String userName) { return false; } - public void localLogin(String userName) { - loginWith(userName); + public void localLogin(String userName, char[] password) { + loginWith(userName, password); } - public void processLoginResponseDataForUser(String userName, LoginResponseData userInfo) { + public void processLoginResponseDataForUser(String userName, char[] password, LoginResponseData userInfo) { String username = userInfo.user != null && StringUtils.isNotBlank(userInfo.user.getUsername()) ? userInfo.user.getUsername() : userName; - boolean loginSuccessful = loginWith(username); + boolean loginSuccessful = loginWith(username, password); saveAnmLocation(getUserLocation(userInfo)); saveAnmTeam(getUserTeam(userInfo)); saveUserInfo(getUserData(userInfo)); @@ -545,7 +554,7 @@ public void saveUserInfo(User user) { * @param userInfo The user's info from the * endpoint (should contain the {team}.{team}.{uuid} key) */ - public Bundle saveUserGroup(String userName, String password, LoginResponseData userInfo) { + public Bundle saveUserGroup(String userName, char[] password, LoginResponseData userInfo) { Bundle bundle = new Bundle(); String username = userInfo.user != null && StringUtils.isNotBlank(userInfo.user.getUsername()) ? userInfo.user.getUsername() : userName; @@ -553,6 +562,9 @@ public Bundle saveUserGroup(String userName, String password, LoginResponseData if (keyStore != null && username != null) { + + byte[] groupId = null; + try { KeyStore.PrivateKeyEntry privateKeyEntry = createUserKeyPair(username); @@ -560,21 +572,19 @@ public Bundle saveUserGroup(String userName, String password, LoginResponseData return null; } - String groupId = null; - SyncConfiguration syncConfiguration = CoreLibrary.getInstance().getSyncConfiguration(); if (syncConfiguration.getEncryptionParam() != null) { SyncFilter syncFilter = syncConfiguration.getEncryptionParam(); if (SyncFilter.TEAM.equals(syncFilter) || SyncFilter.TEAM_ID.equals(syncFilter)) { - groupId = getUserDefaultTeamId(userInfo); + groupId = SecurityHelper.toBytes(getUserDefaultTeamId(userInfo).toCharArray()); } else if (SyncFilter.LOCATION.equals(syncFilter) || SyncFilter.LOCATION_ID.equals(syncFilter)) { - groupId = getUserLocationId(userInfo); + groupId = SecurityHelper.toBytes(getUserLocationId(userInfo).toCharArray()); } else if (SyncFilter.PROVIDER.equals(syncFilter)) { - groupId = username + "-" + password; + groupId = SecurityHelper.toBytes(new StringBuffer(username).append('-').append(password)); } } - if (StringUtils.isBlank(groupId)) { + if (groupId == null || groupId.length < 1) { return null; } @@ -582,8 +592,6 @@ public Bundle saveUserGroup(String userName, String password, LoginResponseData // Then save the encrypted group String encryptedGroupId = encryptString(privateKeyEntry, groupId); - allSharedPreferences.saveEncryptedGroupId(username, encryptedGroupId); - bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, encryptedGroupId); // Finally, save the pioneer user @@ -593,6 +601,10 @@ public Bundle saveUserGroup(String userName, String password, LoginResponseData } } catch (Exception e) { Timber.e(e); + } finally { + + SecurityHelper.clearArray(password); + SecurityHelper.clearArray(groupId); } } @@ -619,10 +631,10 @@ public boolean hasSessionExpired() { return session().hasExpired(); } - protected void setupContextForLogin(String userName, String password) { + protected void setupContextForLogin(char[] password) { session().start(session().lengthInMilliseconds()); configuration.getDrishtiApplication().setPassword(password); - session().setPassword(password); + session().setPassword(SecurityHelper.toBytes(password)); } protected Session session() { @@ -687,12 +699,11 @@ private KeyStore.PrivateKeyEntry createUserKeyPair(final String username) throws * * @param privateKeyEntry Keypair to use to decrypt the string * @param cipherText String to be decrypted - * @return Plain text derived from the cipher text + * @return char array of text derived from the cipher text * @throws Exception */ @VisibleForTesting - protected String decryptString(KeyStore.PrivateKeyEntry privateKeyEntry, String cipherText) - throws Exception { + protected byte[] decryptString(KeyStore.PrivateKeyEntry privateKeyEntry, String cipherText)throws Exception { Cipher output; if (Build.VERSION.SDK_INT >= 23) { @@ -718,18 +729,18 @@ protected String decryptString(KeyStore.PrivateKeyEntry privateKeyEntry, String bytes[i] = values.get(i); } - return new String(bytes, 0, bytes.length, CIPHER_TEXT_CHARACTER_CODE); + return bytes; } /** * Encrypts a string using the provided keypair * * @param privateKeyEntry The keypair to use to encrypt the text - * @param plainText The plain text to encrypt (should be at most 256bytes) + * @param plainTextBytes The plain text to encrypt (should be at most 256bytes) * @return Cipher text corresponding to the plain text * @throws Exception */ - private String encryptString(KeyStore.PrivateKeyEntry privateKeyEntry, String plainText) + private String encryptString(KeyStore.PrivateKeyEntry privateKeyEntry, byte[] plainTextBytes) throws Exception { RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey(); @@ -743,7 +754,7 @@ private String encryptString(KeyStore.PrivateKeyEntry privateKeyEntry, String pl ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, input); - cipherOutputStream.write(plainText.getBytes(CIPHER_TEXT_CHARACTER_CODE)); + cipherOutputStream.write(plainTextBytes); cipherOutputStream.close(); byte[] vals = outputStream.toByteArray(); diff --git a/opensrp-app/src/main/java/org/smartregister/util/Session.java b/opensrp-app/src/main/java/org/smartregister/util/Session.java index 0766aac52..b315dc52f 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/Session.java +++ b/opensrp-app/src/main/java/org/smartregister/util/Session.java @@ -1,6 +1,7 @@ package org.smartregister.util; import org.smartregister.AllConstants; +import org.smartregister.security.SecurityHelper; import java.util.Date; @@ -9,7 +10,7 @@ import static org.joda.time.DateTimeConstants.SECONDS_PER_MINUTE; public class Session { - private String password; + private byte[] password; private String repositoryName = AllConstants.DATABASE_NAME; private long sessionExpiryTime = 0; @@ -17,7 +18,7 @@ public long lengthInMilliseconds() { return 24 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MILLIS_PER_SECOND; } - public String password() { + public byte[] password() { return password; } @@ -25,7 +26,7 @@ public String repositoryName() { return repositoryName; } - public Session setPassword(String password) { + public Session setPassword(byte[] password) { this.password = password; return this; } @@ -51,6 +52,7 @@ public boolean hasExpired() { } public void expire() { + SecurityHelper.clearArray(this.password); setSessionExpiryTimeTo(new Date().getTime() - 1); } diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java index cb45af0dc..b21445e79 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java @@ -28,6 +28,7 @@ import org.joda.time.DateTime; import org.smartregister.R; import org.smartregister.account.AccountHelper; +import org.smartregister.security.SecurityHelper; import org.smartregister.util.SyncUtils; import org.smartregister.util.Utils; import org.smartregister.view.contract.BaseLoginContract; @@ -207,7 +208,7 @@ public void enableLoginButton(boolean isClickable) { public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { if (actionId == R.integer.login || actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) { String username = userNameEditText.getText().toString(); - String password = passwordEditText.getText().toString(); + char[] password = SecurityHelper.readValue(passwordEditText.getText()); mLoginPresenter.attemptLogin(username, password); return true; } @@ -218,7 +219,7 @@ public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent public void onClick(View v) { if (v.getId() == R.id.login_login_btn) { String username = userNameEditText.getText().toString(); - String password = passwordEditText.getText().toString(); + char[] password = SecurityHelper.readValue(passwordEditText.getText()); mLoginPresenter.attemptLogin(username, password); } } diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java index 2a6e31d0b..51a969643 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java @@ -38,7 +38,7 @@ public abstract class DrishtiApplication extends Application { protected Locale locale = null; protected Context context; protected Repository repository; - private String password; + private char[] password; private String username; public static synchronized X getInstance() { @@ -117,7 +117,7 @@ public Repository getRepository() { return repository; } - public String getPassword() { + public char[] getPassword() { if (password == null) { String username = context.userService().getAllSharedPreferences().fetchRegisteredANM(); @@ -127,7 +127,7 @@ public String getPassword() { return password; } - public void setPassword(String password) { + public void setPassword(char[] password) { this.password = password; } diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java index 26a389cb7..ddee8f121 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java @@ -25,6 +25,7 @@ import org.smartregister.domain.LoginResponse; import org.smartregister.domain.jsonmapping.LoginResponseData; import org.smartregister.event.Listener; +import org.smartregister.security.SecurityHelper; import org.smartregister.sync.DrishtiSyncScheduler; import org.smartregister.util.LangUtils; import org.smartregister.util.SyncUtils; @@ -117,7 +118,7 @@ public void login(final View view) { view.setClickable(false); final String userName = userNameEditText.getText().toString(); - final String password = passwordEditText.getText().toString(); + final char[] password = SecurityHelper.readValue(passwordEditText.getText()); if (context.userService().hasARegisteredUser()) { localLogin(view, userName, password); @@ -152,7 +153,7 @@ private void initializeProgressDialog() { progressDialog.setMessage(getString(R.string.loggin_in_dialog_message)); } - private void localLogin(View view, String userName, String password) { + private void localLogin(View view, String userName, char[] password) { try { if (!syncUtils.isAppVersionAllowed()) { showErrorDialog(getString(R.string.outdated_app)); @@ -160,7 +161,7 @@ private void localLogin(View view, String userName, String password) { } if (context.userService().isValidLocalLogin(userName, password)) { - localLoginWith(userName); + localLoginWith(userName, password); } else { showErrorDialog(getString(R.string.login_failed_dialog_message)); view.setClickable(true); @@ -171,7 +172,7 @@ private void localLogin(View view, String userName, String password) { } - private void remoteLogin(final View view, final String userName, final String password) { + private void remoteLogin(final View view, final String userName, final char[] password) { try { if (!syncUtils.isAppVersionAllowed()) { @@ -184,7 +185,7 @@ private void remoteLogin(final View view, final String userName, final String pa tryRemoteLogin(userName, password, new Listener() { public void onEvent(LoginResponse loginResponse) { if (loginResponse == SUCCESS) { - remoteLoginWith(userName, loginResponse.payload()); + remoteLoginWith(userName, password, loginResponse.payload()); } else { if (loginResponse == null) { showErrorDialog("Login failed. Unknown reason. Try Again"); @@ -216,7 +217,7 @@ public void onClick(DialogInterface dialogInterface, int i) { dialog.show(); } - private void tryRemoteLogin(final String userName, final String password, final + private void tryRemoteLogin(final String userName, final char[] password, final Listener afterLoginCheck) { LockingBackgroundTask task = new LockingBackgroundTask(new ProgressIndicator() { @Override @@ -254,14 +255,14 @@ private void hideKeyboard() { inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), HIDE_NOT_ALWAYS); } - private void localLoginWith(String userName) { - context.userService().localLogin(userName); + private void localLoginWith(String userName, char[] password) { + context.userService().localLogin(userName, password); goToHome(); DrishtiSyncScheduler.startOnlyIfConnectedToNetwork(getApplicationContext()); } - private void remoteLoginWith(String userName, LoginResponseData userInfo) { - context.userService().processLoginResponseDataForUser(userName, userInfo); + private void remoteLoginWith(String userName, char[] password, LoginResponseData userInfo) { + context.userService().processLoginResponseDataForUser(userName, password, userInfo); goToHome(); DrishtiSyncScheduler.startOnlyIfConnectedToNetwork(getApplicationContext()); } diff --git a/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java b/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java index a020d8e52..8569a8aa3 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java +++ b/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java @@ -10,7 +10,7 @@ public interface BaseLoginContract { interface Presenter { - void attemptLogin(String username, String password); + void attemptLogin(String username, char[] password); View getLoginView(); @@ -69,14 +69,14 @@ interface Interactor { void onDestroy(boolean isChangingConfiguration); - void login(WeakReference view, String userName, String password); + void login(WeakReference view, String userName, char[] password); } interface Model { boolean isEmptyUsername(String username); - boolean isPasswordValid(String password); + boolean isPasswordValid(char[] password); org.smartregister.Context getOpenSRPContext(); diff --git a/opensrp-app/src/test/java/org/smartregister/TestApplication.java b/opensrp-app/src/test/java/org/smartregister/TestApplication.java index c94c83841..c2b4c43e2 100644 --- a/opensrp-app/src/test/java/org/smartregister/TestApplication.java +++ b/opensrp-app/src/test/java/org/smartregister/TestApplication.java @@ -41,7 +41,7 @@ public void setContext(Context context) { } @Override - public String getPassword() { - return ""; + public char[] getPassword() { + return new char[]{}; } } diff --git a/opensrp-app/src/test/java/org/smartregister/presenter/BaseLoginPresenterTest.java b/opensrp-app/src/test/java/org/smartregister/presenter/BaseLoginPresenterTest.java index fbab93cb1..8f5b27116 100644 --- a/opensrp-app/src/test/java/org/smartregister/presenter/BaseLoginPresenterTest.java +++ b/opensrp-app/src/test/java/org/smartregister/presenter/BaseLoginPresenterTest.java @@ -1,7 +1,6 @@ package org.smartregister.presenter; import android.app.Activity; -import android.content.Context; import android.content.res.Resources; import org.junit.Before; @@ -52,7 +51,7 @@ public void testAttemptLoginShouldFailForUnauthorizedApp() { doReturn(resources).when(context).getResources(); doReturn("string").when(resources).getString(anyInt()); - presenter.attemptLogin("", ""); + presenter.attemptLogin("", "".toCharArray()); verify(view).showErrorDialog(any()); } } diff --git a/opensrp-app/src/test/java/org/smartregister/repository/AllSharedPreferencesTest.java b/opensrp-app/src/test/java/org/smartregister/repository/AllSharedPreferencesTest.java index 0aeed77cf..1807a988f 100644 --- a/opensrp-app/src/test/java/org/smartregister/repository/AllSharedPreferencesTest.java +++ b/opensrp-app/src/test/java/org/smartregister/repository/AllSharedPreferencesTest.java @@ -101,18 +101,6 @@ public void assertsaveCurrentLocality() { Mockito.verify(preferences, Mockito.times(1)).edit(); } - @Test - public void assertfetchEncryptedGroupId() { - Assert.assertNull(allSharedPreferences.fetchEncryptedGroupId(null)); - Assert.assertEquals(allSharedPreferences.fetchEncryptedGroupId("uname"), str); - } - - @Test - public void assertsaveEncryptedGroupId() { - allSharedPreferences.saveEncryptedGroupId("uname", "Id"); - Mockito.verify(preferences, Mockito.times(1)).edit(); - } - @Test public void assertsaveLanguagePreference() { allSharedPreferences.saveLanguagePreference("EN"); diff --git a/opensrp-app/src/test/java/org/smartregister/repository/mock/RepositoryMock.java b/opensrp-app/src/test/java/org/smartregister/repository/mock/RepositoryMock.java index 8430b9d4c..0afbd399f 100644 --- a/opensrp-app/src/test/java/org/smartregister/repository/mock/RepositoryMock.java +++ b/opensrp-app/src/test/java/org/smartregister/repository/mock/RepositoryMock.java @@ -50,7 +50,7 @@ public SQLiteDatabase getWritableDatabase() { } @Override - public boolean canUseThisPassword(String password) { + public boolean canUseThisPassword(char[] password) { return super.canUseThisPassword(password); } diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index be37a2701..1f93c6ea6 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -124,7 +124,7 @@ public class HTTPAgentTest { private HTTPAgent httpAgent; private static final String TEST_USERNAME = "demo"; - private static final String TEST_PASSWORD = "password"; + private static final char[] TEST_PASSWORD = "password".toCharArray(); public static final String TEST_BASE_URL = "https://my-server.com/"; private static final String TEST_TOKEN_ENDPOINT = "https://my-server.com/oauth/token"; private static final String SECURE_RESOURCE_ENDPOINT = "https://my-server.com/my/secure/resource"; @@ -204,21 +204,21 @@ public void testPostPassesGivenCorrectUrl() { @Test public void testUrlCanBeAccessWithGivenCredentials() { PowerMockito.mockStatic(Base64.class); - LoginResponse resp = httpAgent.urlCanBeAccessWithGivenCredentials("http://www.mocky.io/v2/5e54de89310000d559eb33d9", "", ""); + LoginResponse resp = httpAgent.urlCanBeAccessWithGivenCredentials("http://www.mocky.io/v2/5e54de89310000d559eb33d9", "", "".toCharArray()); Assert.assertEquals(LoginResponse.SUCCESS.message(), resp.message()); } @Test public void testUrlCanBeAccessWithGivenCredentialsGivenWrongUrl() { PowerMockito.mockStatic(Base64.class); - LoginResponse resp = httpAgent.urlCanBeAccessWithGivenCredentials("wrong.url", "", ""); + LoginResponse resp = httpAgent.urlCanBeAccessWithGivenCredentials("wrong.url", "", "".toCharArray()); Assert.assertEquals(LoginResponse.MALFORMED_URL.message(), resp.message()); } @Test public void testUrlCanBeAccessWithGivenCredentialsGivenEmptyResp() { PowerMockito.mockStatic(Base64.class); - LoginResponse resp = httpAgent.urlCanBeAccessWithGivenCredentials("http://mockbin.org/bin/e42f7256-18b2-40b9-a20c-40fdc564d06f", "", ""); + LoginResponse resp = httpAgent.urlCanBeAccessWithGivenCredentials("http://mockbin.org/bin/e42f7256-18b2-40b9-a20c-40fdc564d06f", "", "".toCharArray()); Assert.assertEquals(LoginResponse.SUCCESS_WITH_EMPTY_RESPONSE.message(), resp.message()); } @@ -562,9 +562,9 @@ public void testOauth2authenticateRefreshTokenInvokesOauth2authenticateCoreWithC AccountResponse accountResponse = Mockito.mock(AccountResponse.class); - Mockito.doReturn(accountResponse).when(httpAgentSpy).oauth2authenticateCore(ArgumentMatchers.any(StringBuilder.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()); + Mockito.doReturn(accountResponse).when(httpAgentSpy).oauth2authenticateCore(ArgumentMatchers.any(StringBuffer.class), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()); - ArgumentCaptor requestParamStringBuilder = ArgumentCaptor.forClass(StringBuilder.class); + ArgumentCaptor requestParamStringBuilder = ArgumentCaptor.forClass(StringBuffer.class); ArgumentCaptor grantType = ArgumentCaptor.forClass(String.class); ArgumentCaptor tokenEndPoint = ArgumentCaptor.forClass(String.class); diff --git a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java index 0830efa34..4f6cf82aa 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java @@ -120,13 +120,13 @@ public void shouldUseHttpAgentToDoRemoteLoginCheck() { when(httpAgent.urlCanBeAccessWithGivenCredentials( httpAuthenticateUrl, user, - password)).thenReturn(loginResponse); + password.toCharArray())).thenReturn(loginResponse); when(allSharedPreferences.fetchRegisteredANM()).thenReturn("user"); - userService.isValidRemoteLogin(user, password); + userService.isValidRemoteLogin(user, password.toCharArray()); - verify(httpAgent).urlCanBeAccessWithGivenCredentials(httpAuthenticateUrl, user, password); + verify(httpAgent).urlCanBeAccessWithGivenCredentials(httpAuthenticateUrl, user, password.toCharArray()); } @Test @@ -163,19 +163,19 @@ public void shouldSaveANMLocation() { public void shouldConsiderALocalLoginValid() { // When Username Matches Registered User And Password Matches The One In DB when(allSharedPreferences.fetchRegisteredANM()).thenReturn("ANM X"); - when(repository.canUseThisPassword("password Z")).thenReturn(true); + when(repository.canUseThisPassword("password Z".toCharArray())).thenReturn(true); - assertTrue(userService.isValidLocalLogin("ANM X", "password Z")); + assertTrue(userService.isValidLocalLogin("ANM X", "password Z".toCharArray())); verify(allSharedPreferences).fetchRegisteredANM(); - verify(repository).canUseThisPassword("password Z"); + verify(repository).canUseThisPassword("password Z".toCharArray()); } @Test public void shouldConsiderALocalLoginInvalidWhenRegisteredUserDoesNotMatch() { when(allSharedPreferences.fetchRegisteredANM()).thenReturn("ANM X"); - assertFalse(userService.isValidLocalLogin("SOME OTHER ANM", "password")); + assertFalse(userService.isValidLocalLogin("SOME OTHER ANM", "password".toCharArray())); verify(allSharedPreferences).fetchRegisteredANM(); verifyZeroInteractions(repository); @@ -184,12 +184,12 @@ public void shouldConsiderALocalLoginInvalidWhenRegisteredUserDoesNotMatch() { @Test public void shouldConsiderALocalLoginInvalidWhenRegisteredUserMatchesButNotThePassword() { when(allSharedPreferences.fetchRegisteredANM()).thenReturn("ANM X"); - when(repository.canUseThisPassword("password Z")).thenReturn(false); + when(repository.canUseThisPassword("password Z".toCharArray())).thenReturn(false); - assertFalse(userService.isValidLocalLogin("ANM X", "password Z")); + assertFalse(userService.isValidLocalLogin("ANM X", "password Z".toCharArray())); verify(allSharedPreferences).fetchRegisteredANM(); - verify(repository).canUseThisPassword("password Z"); + verify(repository).canUseThisPassword("password Z".toCharArray()); } @Test @@ -209,7 +209,7 @@ public void logoutCurrentUser() { when(allSharedPreferences.fetchRegisteredANM()).thenReturn("user X"); - userService.processLoginResponseDataForUser("user X", userInfo); + userService.processLoginResponseDataForUser("user X", "password Y".toCharArray(), userInfo); verify(allSettings).registerANM("user X"); } diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityTest.java index 8c20a2818..624f3de9d 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityTest.java @@ -109,7 +109,7 @@ public void assertActivityNotNull() { @Test public void localLoginTest() { Mockito.doReturn(true).when(userService).hasARegisteredUser(); - Mockito.doReturn(true).when(userService).isValidLocalLogin(anyString(), anyString()); + Mockito.doReturn(true).when(userService).isValidLocalLogin(anyString(), any(char[].class)); Mockito.doReturn(allSharedPreferences).when(context_).allSharedPreferences(); EditText username = activity.findViewById(R.id.login_userNameText); @@ -121,7 +121,7 @@ public void localLoginTest() { Button login_button = activity.findViewById(R.id.login_loginButton); login_button.performClick(); - Mockito.verify(userService, Mockito.atLeastOnce()).localLogin(anyString()); + Mockito.verify(userService, Mockito.atLeastOnce()).localLogin(anyString(), any(char[].class)); } diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityWithRemoteLoginTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityWithRemoteLoginTest.java index 754739d50..3dd2d2359 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityWithRemoteLoginTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityWithRemoteLoginTest.java @@ -126,8 +126,8 @@ private void destroyController() { @Test public void remoteLoginTest() { when(userService.hasARegisteredUser()).thenReturn(false); - when(userService.isValidLocalLogin(anyString(), anyString())).thenReturn(true); - when(userService.isValidRemoteLogin(anyString(), anyString())).thenReturn(LoginResponse.SUCCESS.withPayload(new LoginResponseData())); + when(userService.isValidLocalLogin(anyString(), any(char[].class))).thenReturn(true); + when(userService.isValidRemoteLogin(anyString(),any(char[].class))).thenReturn(LoginResponse.SUCCESS.withPayload(new LoginResponseData())); when(context_.allSharedPreferences()).thenReturn(allSharedPreferences); when(allSharedPreferences.fetchBaseURL(anyString())).thenReturn("base url"); EditText username = activity.findViewById(R.id.login_userNameText); @@ -136,7 +136,7 @@ public void remoteLoginTest() { password.setText("password"); Button login_button = activity.findViewById(R.id.login_loginButton); login_button.performClick(); - Mockito.verify(userService, Mockito.atLeastOnce()).processLoginResponseDataForUser(anyString(), any(LoginResponseData.class)); + Mockito.verify(userService, Mockito.atLeastOnce()).processLoginResponseDataForUser(anyString(),any(char[].class), any(LoginResponseData.class)); destroyController(); From b09d8613fc02fec023ee63634ac5b78e43501e71 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 9 Jun 2020 10:46:42 +0300 Subject: [PATCH 24/70] Fix unit test --- .../src/main/java/org/smartregister/service/HTTPAgent.java | 6 +++--- .../test/java/org/smartregister/service/HTTPAgentTest.java | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index 9a9e962bd..de60b0f37 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -8,8 +8,8 @@ import com.google.common.io.BaseEncoding; import com.google.gson.Gson; +import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; import org.apache.http.util.ByteArrayBuffer; @@ -232,7 +232,7 @@ public LoginResponse urlCanBeAccessWithGivenCredentials(String requestURL, Strin url = requestURL.replaceAll("\\s+", ""); urlConnection = initializeHttp(url, false); - byte[] credentials = SecurityHelper.toBytes(new StringBuffer(userName).append(':').append(password));//(new StringBuffer(userName).append(':').append(password)).toString().getBytes(); + byte[] credentials = SecurityHelper.toBytes(new StringBuffer(userName).append(':').append(password)); final String basicAuth = AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + Base64.encodeToString(credentials, Base64.NO_WRAP); urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, basicAuth); int statusCode = urlConnection.getResponseCode(); @@ -568,7 +568,7 @@ public AccountResponse oauth2authenticateCore(StringBuffer requestParamBuffer, S String clientId = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientId(); String clientSecret = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientSecret(); - final String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes()); + final String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes(CharEncoding.UTF_8)); requestParamBuffer.append("&grant_type=").append(grantType); requestParamBuffer.append("&client_id=").append(clientId); diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index 1f93c6ea6..905b71524 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -7,6 +7,7 @@ import com.google.common.io.BaseEncoding; +import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.json.JSONObject; import org.junit.Assert; @@ -300,9 +301,9 @@ public void testOauth2authenticateCreatesUrlConnectionWithCorrectParameters() th Mockito.verify(httpURLConnection).setConnectTimeout(60000); Mockito.verify(httpURLConnection).setReadTimeout(60000); - String requestParams = "&grant_type=" + AccountHelper.OAUTH.GRANT_TYPE.PASSWORD + "&username=" + TEST_USERNAME + "&password=" + TEST_PASSWORD + "&client_id=" + TEST_CLIENT_ID + "&client_secret=" + TEST_CLIENT_SECRET; + String requestParams = "&grant_type=" + AccountHelper.OAUTH.GRANT_TYPE.PASSWORD + "&username=" + TEST_USERNAME + "&password=" + String.valueOf(TEST_PASSWORD) + "&client_id=" + TEST_CLIENT_ID + "&client_secret=" + TEST_CLIENT_SECRET; - Mockito.verify(httpURLConnection).setFixedLengthStreamingMode(requestParams.getBytes().length); + Mockito.verify(httpURLConnection).setFixedLengthStreamingMode(requestParams.getBytes(CharEncoding.UTF_8).length); Mockito.verify(httpURLConnection).setDoOutput(true); Mockito.verify(httpURLConnection).setInstanceFollowRedirects(false); Mockito.verify(httpURLConnection).setRequestMethod("POST"); @@ -310,7 +311,7 @@ public void testOauth2authenticateCreatesUrlConnectionWithCorrectParameters() th Mockito.verify(httpURLConnection).setRequestProperty("charset", "utf-8"); Mockito.verify(httpURLConnection).setRequestProperty(ArgumentMatchers.eq("Content-Length"), ArgumentMatchers.anyString()); Mockito.verify(httpURLConnection).setUseCaches(false); - final String base64Auth = BaseEncoding.base64().encode(new String(TEST_CLIENT_ID + ":" + TEST_CLIENT_SECRET).getBytes()); + final String base64Auth = BaseEncoding.base64().encode(new String(TEST_CLIENT_ID + ":" + TEST_CLIENT_SECRET).getBytes(CharEncoding.UTF_8)); Mockito.verify(httpURLConnection).setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + base64Auth); Mockito.verify(httpURLConnection).setInstanceFollowRedirects(false); From 658aef8e2b4a61f2563a329e6252347327abc472 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 9 Jun 2020 14:00:00 +0300 Subject: [PATCH 25/70] HTTP Agent unit tests - Testing Tuesday - Add unit test for http image post/ upload --- .../org/smartregister/service/HTTPAgent.java | 21 ++++- .../smartregister/service/HTTPAgentTest.java | 85 +++++++++++++++++++ 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index de60b0f37..4c5c6a7b2 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -48,6 +48,7 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.ProtocolException; @@ -83,7 +84,7 @@ public class HTTPAgent { - private static final int FILE_UPLOAD_CHUNK_SIZE_BYTES = 4096; + public static final int FILE_UPLOAD_CHUNK_SIZE_BYTES = 4096; private Context context; private AllSharedPreferences allSharedPreferences; @@ -375,7 +376,7 @@ public String httpImagePost(String urlString, ProfileImage image) { httpUrlConnection.setChunkedStreamingMode(FILE_UPLOAD_CHUNK_SIZE_BYTES); outputStream = httpUrlConnection.getOutputStream(); - writer = new PrintWriter(new OutputStreamWriter(outputStream, "UTF-8"), true); + writer = getPrintWriter(outputStream); // attach image attachImage(writer, image, outputStream); @@ -427,6 +428,12 @@ public String httpImagePost(String urlString, ProfileImage image) { return responseString; } + @VisibleForTesting + @NonNull + protected PrintWriter getPrintWriter(OutputStream outputStream) throws UnsupportedEncodingException { + return new PrintWriter(new OutputStreamWriter(outputStream, CharEncoding.UTF_8), true); + } + private void attachImage(PrintWriter writer, ProfileImage image, OutputStream outputStream) throws IOException { File uploadImageFile = getDownloadFolder(image.getFilepath()); String fileName = uploadImageFile.getName(); @@ -438,7 +445,7 @@ private void attachImage(PrintWriter writer, ProfileImage image, OutputStream ou writer.append(crlf); writer.flush(); - FileInputStream inputStream = new FileInputStream(uploadImageFile); + FileInputStream inputStream = getFileInputStream(uploadImageFile); byte[] buffer = new byte[FILE_UPLOAD_CHUNK_SIZE_BYTES]; int bytesRead = -1; while ((bytesRead = inputStream.read(buffer)) != -1) { @@ -568,7 +575,7 @@ public AccountResponse oauth2authenticateCore(StringBuffer requestParamBuffer, S String clientId = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientId(); String clientSecret = CoreLibrary.getInstance().getSyncConfiguration().getOauthClientSecret(); - final String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes(CharEncoding.UTF_8)); + final String base64Auth = BaseEncoding.base64().encode(new StringBuffer(clientId).append(':').append(clientSecret).toString().getBytes(CharEncoding.UTF_8)); requestParamBuffer.append("&grant_type=").append(grantType); requestParamBuffer.append("&client_id=").append(clientId); @@ -787,6 +794,12 @@ public Response downloadFromURL(String downloadURL_, String file return new Response(ResponseStatus.success, DownloadStatus.downloaded); } + @VisibleForTesting + @NonNull + protected FileInputStream getFileInputStream(File uploadImageFile) throws FileNotFoundException { + return new FileInputStream(uploadImageFile); + } + @VisibleForTesting @NonNull protected FileOutputStream getFileOutputStream(File file) throws FileNotFoundException { diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index 905b71524..bc5de2873 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -48,6 +48,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.SocketTimeoutException; @@ -105,6 +106,9 @@ public class HTTPAgentTest { @Mock private InputStream inputStream; + @Mock + private FileInputStream fileInputStream; + @Mock private InputStream errorStream; @@ -120,6 +124,9 @@ public class HTTPAgentTest { @Mock private FileOutputStream fileOutputStream; + @Mock + private PrintWriter printWriter; + @Rule private TemporaryFolder folder = new TemporaryFolder(); @@ -132,6 +139,10 @@ public class HTTPAgentTest { private static final String KEYClOAK_CONFIGURATION_ENDPOINT = "https://my-server.com/rest/config/keycloak"; private static final String USER_DETAILS_ENDPOINT = "https://my-server.com/opensrp/security/authenticate"; private static final String TEST_IMAGE_DOWNLOAD_ENDPOINT = "https://my-server.com/opensrp/multimedia/myimage.jpg"; + private static final String TEST_IMAGE_UPLOAD_ENDPOINT = "https://my-server.com/opensrp/multimedia/"; + private static final String TEST_IMAGE_FILE_PATH = "file://usr/sdcard/dev0/data/org.smartregister.core/localimage.jpg"; + protected static final String TEST_BASE_ENTITY_ID = "23ka2-3e23h2-n3g2i4-9q3b-yts4-20"; + protected static final String TEST_ANM_ID = "demo"; private final String SAMPLE_TEST_TOKEN = "sample-test-token"; private final String SAMPLE_REFRESH_TOKEN = "sample-refresh-token"; @@ -164,6 +175,7 @@ public void setUp() { PowerMockito.mockStatic(AccountHelper.class); PowerMockito.when(AccountHelper.getOauthAccountByType(accountAuthenticatorXml.getAccountType())).thenReturn(account); + PowerMockito.when(AccountHelper.getOAuthToken(accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); httpAgent = new HTTPAgent(context, allSharedPreferences, dristhiConfiguration); httpAgent.setConnectTimeout(60000); @@ -1014,4 +1026,77 @@ public void testDownloadFromUrlReturnsCorrectResponseIfConnectionStatusIsNOT200( } + @Test + public void testHttpImagePostConfiguresConnectionRequestCorrectly() throws Exception { + + PowerMockito.mockStatic(Base64.class); + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_IMAGE_UPLOAD_ENDPOINT); + Mockito.doReturn(outputStream).when(httpURLConnection).getOutputStream(); + Mockito.doReturn(HttpURLConnection.HTTP_OK).when(httpURLConnection).getResponseCode(); + Mockito.doReturn(file).when(httpAgentSpy).getDownloadFolder(TEST_IMAGE_FILE_PATH); + Mockito.doReturn(fileInputStream).when(httpAgentSpy).getFileInputStream(file); + Mockito.doReturn(inputStream).when(httpURLConnection).getInputStream(); + Mockito.doReturn(-1).when(fileInputStream).read(ArgumentMatchers.any(byte[].class)); + + Mockito.doReturn(printWriter).when(httpAgentSpy).getPrintWriter(outputStream); + Mockito.doReturn("myFileName").when(file).getName(); + Mockito.doReturn(printWriter).when(printWriter).append(ArgumentMatchers.any(CharSequence.class)); + + ProfileImage profileImage = new ProfileImage(); + profileImage.setFilepath(TEST_IMAGE_FILE_PATH); + profileImage.setAnmId(TEST_ANM_ID); + profileImage.setEntityID(TEST_BASE_ENTITY_ID); + profileImage.setContenttype("png"); + profileImage.setFilecategory("coverpic"); + + httpAgentSpy.httpImagePost(TEST_IMAGE_UPLOAD_ENDPOINT, profileImage); + + Mockito.verify(httpURLConnection).setDoOutput(true); + Mockito.verify(httpURLConnection).setDoInput(true); + Mockito.verify(httpURLConnection).setRequestMethod("POST"); + + ArgumentCaptor requestAttributeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor requestValueCaptor = ArgumentCaptor.forClass(String.class); + + Mockito.verify(httpURLConnection, Mockito.times(2)).setRequestProperty(requestAttributeCaptor.capture(), requestValueCaptor.capture()); + List requestAttributeCaptorValues = requestAttributeCaptor.getAllValues(); + List requestValueCaptorValues = requestValueCaptor.getAllValues(); + + Assert.assertEquals("Authorization", requestAttributeCaptorValues.get(0)); + Assert.assertEquals("Bearer " + SAMPLE_TEST_TOKEN, requestValueCaptorValues.get(0)); + + Assert.assertEquals("Content-Type", requestAttributeCaptorValues.get(1)); + Assert.assertTrue(requestValueCaptorValues.get(1).startsWith("multipart/form-data;boundary=")); + + Mockito.verify(httpURLConnection).setUseCaches(false); + Mockito.verify(httpURLConnection).setChunkedStreamingMode(HTTPAgent.FILE_UPLOAD_CHUNK_SIZE_BYTES); + + //Attach Image + Mockito.verify(httpAgentSpy).getDownloadFolder(TEST_IMAGE_FILE_PATH); + + ArgumentCaptor printWriterCaptor = ArgumentCaptor.forClass(CharSequence.class); + + Mockito.verify(printWriter, Mockito.times(49)).append(printWriterCaptor.capture()); + + List printWriterAppendedValues = printWriterCaptor.getAllValues(); + + Assert.assertTrue(printWriterAppendedValues.contains("Content-Disposition: form-data; name=\"file\"; filename=\"myFileName\"")); + Assert.assertTrue(printWriterAppendedValues.contains("Content-Disposition: form-data; name=\"anm-id\"")); + Assert.assertTrue(printWriterAppendedValues.contains("Content-Disposition: form-data; name=\"entity-id\"")); + Assert.assertTrue(printWriterAppendedValues.contains("Content-Disposition: form-data; name=\"file-category\"")); + Assert.assertTrue(printWriterAppendedValues.contains("Content-Disposition: form-data; name=\"content-type\"")); + Assert.assertTrue(printWriterAppendedValues.contains(profileImage.getAnmId())); + Assert.assertTrue(printWriterAppendedValues.contains(profileImage.getEntityID())); + Assert.assertTrue(printWriterAppendedValues.contains(profileImage.getFilecategory())); + Assert.assertTrue(printWriterAppendedValues.contains(profileImage.getContenttype())); + Assert.assertTrue(printWriterAppendedValues.contains("Content-Type: text/plain; charset=UTF-8")); + + Mockito.verify(printWriter, Mockito.times(7)).flush(); + } } From c8d011445cb24bc0418f70c18335824392a7c112 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Thu, 11 Jun 2020 15:51:28 +0300 Subject: [PATCH 26/70] Refactor Verify Authorization - Add support for verifying if account disabled by Admin - Update logout --- gradle.properties | 4 ++ .../account/AccountConfiguration.java | 11 ++++ .../smartregister/account/AccountHelper.java | 1 + .../account/AccountUserInfo.java | 61 +++++++++++++++++++ .../login/task/RemoteLoginTask.java | 1 + .../org/smartregister/service/HTTPAgent.java | 41 +++++++++---- 6 files changed, 106 insertions(+), 13 deletions(-) create mode 100644 opensrp-app/src/main/java/org/smartregister/account/AccountUserInfo.java diff --git a/gradle.properties b/gradle.properties index ac928efda..c7676bb34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,8 @@ +<<<<<<< HEAD VERSION_NAME=2.0.4-SNAPSHOT +======= +VERSION_NAME=2.0.0-PRV-SNAPSHOT +>>>>>>> Refactor Verify Authorization VERSION_CODE=1 GROUP=org.smartregister POM_SETTING_DESCRIPTION=OpenSRP Client Core Application diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountConfiguration.java b/opensrp-app/src/main/java/org/smartregister/account/AccountConfiguration.java index 171d9a47b..705a5bbf0 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountConfiguration.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountConfiguration.java @@ -18,6 +18,9 @@ public class AccountConfiguration { @SerializedName("token_endpoint") private String tokenEndpoint; + @SerializedName("userinfo_endpoint") + private String userinfoEndpoint; + @SerializedName("grant_types_supported") private List grantTypesSupported; @@ -33,6 +36,10 @@ public String getTokenEndpoint() { return tokenEndpoint; } + public String getUserinfoEndpoint() { + return userinfoEndpoint; + } + public List getGrantTypesSupported() { return grantTypesSupported; } @@ -49,6 +56,10 @@ public void setTokenEndpoint(String tokenEndpoint) { this.tokenEndpoint = tokenEndpoint; } + public void setUserinfoEndpoint(String userinfoEndpoint) { + this.userinfoEndpoint = userinfoEndpoint; + } + public void setGrantTypesSupported(List grantTypesSupported) { this.grantTypesSupported = grantTypesSupported; } diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java index 4a0b4a06b..aad68d1c1 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java @@ -23,6 +23,7 @@ public static final class CONFIGURATION_CONSTANTS { public final static String TOKEN_ENDPOINT_URL = "token_endpoint_url"; public final static String AUTHORIZATION_ENDPOINT_URL = "authorization_endpoint_url"; public final static String ISSUER_ENDPOINT_URL = "issuer_endpoint_url"; + public static final String USERINFO_ENDPOINT_URL = "userinfo_endpint_url"; } public static final class OAUTH { diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountUserInfo.java b/opensrp-app/src/main/java/org/smartregister/account/AccountUserInfo.java new file mode 100644 index 000000000..4f80cd2ca --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountUserInfo.java @@ -0,0 +1,61 @@ +package org.smartregister.account; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by ndegwamartin on 03/06/2020. + */ +public class AccountUserInfo { + + private String name; + + private String email; + + private Boolean enabled; + + @SerializedName("preferred_username") + private String preferredUsername; + + @SerializedName("email_verified") + private Boolean emailVerified; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public String getPreferredUsername() { + return preferredUsername; + } + + public void setPreferredUsername(String preferredUsername) { + this.preferredUsername = preferredUsername; + } + + public Boolean getEmailVerified() { + return emailVerified; + } + + public void setEmailVerified(Boolean emailVerified) { + this.emailVerified = emailVerified; + } +} diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index 280a181e9..4e39e218d 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -85,6 +85,7 @@ protected LoginResponse doInBackground(Void... params) { sharedPrefEditor.putString(AccountHelper.CONFIGURATION_CONSTANTS.TOKEN_ENDPOINT_URL, accountConfiguration.getTokenEndpoint()); sharedPrefEditor.putString(AccountHelper.CONFIGURATION_CONSTANTS.AUTHORIZATION_ENDPOINT_URL, accountConfiguration.getAuthorizationEndpoint()); sharedPrefEditor.putString(AccountHelper.CONFIGURATION_CONSTANTS.ISSUER_ENDPOINT_URL, accountConfiguration.getIssuerEndpoint()); + sharedPrefEditor.putString(AccountHelper.CONFIGURATION_CONSTANTS.USERINFO_ENDPOINT_URL, accountConfiguration.getUserinfoEndpoint()); sharedPrefEditor.apply(); AccountResponse response = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticate(mUsername, mPassword, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, accountConfiguration.getTokenEndpoint()); diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index 4c5c6a7b2..8b52fc44c 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -21,6 +21,7 @@ import org.smartregister.account.AccountError; import org.smartregister.account.AccountHelper; import org.smartregister.account.AccountResponse; +import org.smartregister.account.AccountUserInfo; import org.smartregister.compression.GZIPCompression; import org.smartregister.domain.DownloadStatus; import org.smartregister.domain.LoginResponse; @@ -831,34 +832,47 @@ protected File getDownloadFolder(String sdcardPathDownload) { public boolean verifyAuthorization() { - String baseUrl = configuration.dristhiBaseURL(); - - if (baseUrl.endsWith("/")) { - baseUrl = baseUrl.substring(0, baseUrl.length() - 1); - } - final String username = allSharedPreferences.fetchRegisteredANM(); - baseUrl = baseUrl + DETAILS_URL + username; + String userInfoUrl = allSharedPreferences.getPreferences().getString(AccountHelper.CONFIGURATION_CONSTANTS.USERINFO_ENDPOINT_URL, ""); HttpURLConnection urlConnection = null; + InputStream inputStream = null; + try { - urlConnection = initializeHttp(baseUrl, true); + urlConnection = initializeHttp(userInfoUrl, true); - int statusCode = urlConnection.getResponseCode(); + AccountUserInfo userInfo = null; if (HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode()) { invalidateExpiredCachedAccessToken(); - urlConnection = initializeHttp(baseUrl, true); + urlConnection = initializeHttp(userInfoUrl, true); - Timber.i("User not authorized. User access was revoked, will log off user"); - return false; - } else if (statusCode != HttpStatus.SC_OK) { + } + + if (urlConnection.getResponseCode() == HttpStatus.SC_OK) { + + inputStream = urlConnection.getInputStream(); + + String responseString = IOUtils.toString(inputStream); + + userInfo = gson.fromJson(responseString, AccountUserInfo.class); + + } else { Timber.w("Error occurred verifying authorization, User will not be logged off"); + } + + if (!userInfo.getEnabled()) { + + Timber.i("User not authorized. User access was revoked, will log off user"); + return true; + } else { + Timber.i("User is Authorized"); + return false; } } catch (IOException e) { @@ -866,6 +880,7 @@ public boolean verifyAuthorization() { } finally { closeConnection(urlConnection); + closeIOStream(inputStream); } return true; } From c2bbcc546fd46ef0326f4143f8f977e81911f696 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Thu, 11 Jun 2020 22:33:09 +0300 Subject: [PATCH 27/70] Update Verify Authorization - Add backward compatibility for legacy implementation - Add unit tests - Fix codacy issues --- .../login/task/RemoteLoginTask.java | 2 +- .../org/smartregister/service/HTTPAgent.java | 51 +++++++++++++++- .../smartregister/service/UserService.java | 16 +---- .../smartregister/service/HTTPAgentTest.java | 58 ++++++++++++++++++- 4 files changed, 107 insertions(+), 20 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index 4e39e218d..2dcbae997 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -111,7 +111,7 @@ protected LoginResponse doInBackground(Void... params) { SyncSettingsServiceHelper syncSettingsServiceHelper = new SyncSettingsServiceHelper(getOpenSRPContext().configuration().dristhiBaseURL(), getOpenSRPContext().getHttpAgent()); try { - JSONArray settings = syncSettingsServiceHelper.pullSettingsFromServer(Utils.getFilterValue(loginResponse, CoreLibrary.getInstance().getSyncConfiguration().getSyncFilterParam()),response.getAccessToken()); + JSONArray settings = syncSettingsServiceHelper.pullSettingsFromServer(Utils.getFilterValue(loginResponse, CoreLibrary.getInstance().getSyncConfiguration().getSyncFilterParam()), response.getAccessToken()); JSONObject prefSettingsData = new JSONObject(); prefSettingsData.put(AllConstants.PREF_KEY.SETTINGS, settings); diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index 8b52fc44c..a3db60634 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -864,14 +864,17 @@ public boolean verifyAuthorization() { Timber.w("Error occurred verifying authorization, User will not be logged off"); } - if (!userInfo.getEnabled()) { + if (userInfo == null || userInfo.getEnabled() == null) + return verifyAuthorizationLegacy(); - Timber.i("User not authorized. User access was revoked, will log off user"); + if (userInfo.getEnabled()) { + + Timber.i("User is Authorized"); return true; } else { - Timber.i("User is Authorized"); + Timber.i("User not authorized. User access was revoked, will log off user"); return false; } @@ -885,6 +888,48 @@ public boolean verifyAuthorization() { return true; } + //For backward compatibility + public boolean verifyAuthorizationLegacy() { + + String baseUrl = configuration.dristhiBaseURL(); + + if (baseUrl.endsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + } + final String username = allSharedPreferences.fetchRegisteredANM(); + baseUrl = baseUrl + DETAILS_URL + username; + + HttpURLConnection urlConnection = null; + + try { + + urlConnection = initializeHttp(baseUrl, true); + + int statusCode = urlConnection.getResponseCode(); + + if (HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode()) { + + invalidateExpiredCachedAccessToken(); + + urlConnection = initializeHttp(baseUrl, true); + + Timber.i("User not authorized. User access was revoked, will log off user"); + return false; + } else if (statusCode != HttpStatus.SC_OK) { + Timber.w("Error occurred verifying authorization, User will not be logged off"); + } else { + Timber.i("User is Authorized"); + } + + } catch (IOException e) { + Timber.e(e); + } finally { + + closeConnection(urlConnection); + } + return true; + } + public AccountConfiguration fetchOAuthConfiguration() { String baseUrl = configuration.dristhiBaseURL(); diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index e105de17f..06e700204 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -323,7 +323,7 @@ public Response getLocationInformation() { private boolean loginWith(String userName, char[] password) { boolean loginSuccessful = true; - if (usesGroupIdAsDBPassword(userName)) { + if (usesGroupIdAsDBPassword()) { String encryptedGroupId = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); @@ -353,20 +353,10 @@ private boolean loginWith(String userName, char[] password) { /** * Checks whether to use the groupId for the current user to decrypt the database * - * @param userName The user to check * @return TRUE if the user decrypts the database using the groupId */ - private boolean usesGroupIdAsDBPassword(String userName) { - try { - if (keyStore != null && keyStore.containsAlias(userName)) { - if (AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()) != null) { - return true; - } - } - } catch (Exception e) { - Timber.e(e); - } - return false; + private boolean usesGroupIdAsDBPassword() { + return AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()) != null; } public void localLogin(String userName, char[] password) { diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index bc5de2873..3a8f0ad83 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -140,6 +140,7 @@ public class HTTPAgentTest { private static final String USER_DETAILS_ENDPOINT = "https://my-server.com/opensrp/security/authenticate"; private static final String TEST_IMAGE_DOWNLOAD_ENDPOINT = "https://my-server.com/opensrp/multimedia/myimage.jpg"; private static final String TEST_IMAGE_UPLOAD_ENDPOINT = "https://my-server.com/opensrp/multimedia/"; + private static final String TEST_USER_INFO_ENDPOINT = "https://keycloak.my-server.com/auth/userinfo"; private static final String TEST_IMAGE_FILE_PATH = "file://usr/sdcard/dev0/data/org.smartregister.core/localimage.jpg"; protected static final String TEST_BASE_ENTITY_ID = "23ka2-3e23h2-n3g2i4-9q3b-yts4-20"; protected static final String TEST_ANM_ID = "demo"; @@ -154,6 +155,7 @@ public class HTTPAgentTest { private static final String OAUTH_CONFIGURATION_SERVER_RESPONSE = "{\"issuer\":\"https://my-server.com/oauth/issuer\",\r\n\"authorization_endpoint\": \"https://my-server.com/oauth/auth\",\r\n\"token_endpoint\": \"https://my-server.com/oauth/token\",\r\n\"grant_types_supported\":[\"authorization code\",\"implicit\",\"password\"]\r\n}"; private static final String FETCH_DATA_REQUEST_SERVER_RESPONSE = "{status:{\"response_status\":\"success\"},payload: \"My secure resources from the server\"\r\n\r\n}"; private static final String SAMPLE_POST_REQUEST_PAYLOAD = "{\"payload\":\"My POST Payload\"}"; + private static final String ACCOUNT_INFO_REQUEST_SERVER_RESPONSE = "{ \"name\":\"Test User\", \"email\":\"demo@smartegister.org\", \"enabled\":true, \"preferred_username\":\"demo\", \"email_verified\":true}"; private static final String TEST_FILE_NAME = "Profile"; @Before @@ -772,7 +774,7 @@ public void testFetchUserDetailsConstructsCorrectResponseForRequestsWithoutNetwo @Test - public void testVerifyAuthorizationReturnsTrueForAuthorizedResponse() throws Exception { + public void testVerifyAuthorizationLegacyReturnsTrueForAuthorizedResponse() throws Exception { URL url = PowerMockito.mock(URL.class); Assert.assertNotNull(url); @@ -784,13 +786,13 @@ public void testVerifyAuthorizationReturnsTrueForAuthorizedResponse() throws Exc Mockito.doReturn(HttpURLConnection.HTTP_OK).when(httpURLConnection).getResponseCode(); - boolean isVerified = httpAgentSpy.verifyAuthorization(); + boolean isVerified = httpAgentSpy.verifyAuthorizationLegacy(); Assert.assertTrue(isVerified); } @Test - public void testVerifyAuthorizationReturnsFalseForUnauthorizedResponse() throws Exception { + public void testVerifyAuthorizationLegacyReturnsFalseForUnauthorizedResponse() throws Exception { URL url = PowerMockito.mock(URL.class); Assert.assertNotNull(url); @@ -803,6 +805,56 @@ public void testVerifyAuthorizationReturnsFalseForUnauthorizedResponse() throws Mockito.doReturn(HttpURLConnection.HTTP_UNAUTHORIZED).when(httpURLConnection).getResponseCode(); + boolean isVerified = httpAgentSpy.verifyAuthorizationLegacy(); + Assert.assertFalse(isVerified); + + } + + + @Test + public void testVerifyAuthorizationReturnsTrueForAuthorizedResponse() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(sharedPreferences).when(allSharedPreferences).getPreferences(); + + Mockito.doReturn(TEST_USER_INFO_ENDPOINT).when(sharedPreferences).getString(AccountHelper.CONFIGURATION_CONSTANTS.USERINFO_ENDPOINT_URL, ""); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_USER_INFO_ENDPOINT); + + Mockito.doReturn(HttpURLConnection.HTTP_OK).when(httpURLConnection).getResponseCode(); + + Mockito.doReturn(inputStream).when(httpURLConnection).getInputStream(); + + PowerMockito.mockStatic(IOUtils.class); + PowerMockito.when(IOUtils.toString(inputStream)).thenReturn(ACCOUNT_INFO_REQUEST_SERVER_RESPONSE); + + boolean isVerified = httpAgentSpy.verifyAuthorization(); + Assert.assertTrue(isVerified); + + } + + @Test + public void testVerifyAuthorizationReturnsFalseForUnauthorizedResponse() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(sharedPreferences).when(allSharedPreferences).getPreferences(); + + Mockito.doReturn(TEST_USER_INFO_ENDPOINT).when(sharedPreferences).getString(AccountHelper.CONFIGURATION_CONSTANTS.USERINFO_ENDPOINT_URL, ""); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_USER_INFO_ENDPOINT); + + Mockito.doReturn(false).when(httpAgentSpy).verifyAuthorizationLegacy(); + + Mockito.doReturn(HttpURLConnection.HTTP_UNAUTHORIZED).when(httpURLConnection).getResponseCode(); + boolean isVerified = httpAgentSpy.verifyAuthorization(); Assert.assertFalse(isVerified); From 34c14a876e4d3a786fff18e134c0068bded65a9a Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Fri, 12 Jun 2020 08:48:33 +0300 Subject: [PATCH 28/70] Update artifact to correct version --- gradle.properties | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index c7676bb34..ac928efda 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,4 @@ -<<<<<<< HEAD VERSION_NAME=2.0.4-SNAPSHOT -======= -VERSION_NAME=2.0.0-PRV-SNAPSHOT ->>>>>>> Refactor Verify Authorization VERSION_CODE=1 GROUP=org.smartregister POM_SETTING_DESCRIPTION=OpenSRP Client Core Application From 0de5f3580c9a2747e90c97317e1e59d82fc4c50e Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Fri, 12 Jun 2020 09:09:29 +0300 Subject: [PATCH 29/70] Update Arabic and French translations --- opensrp-app/res/values-ar/strings.xml | 4 ++-- opensrp-app/res/values-fr/strings.xml | 4 ++-- opensrp-app/res/values/strings.xml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/opensrp-app/res/values-ar/strings.xml b/opensrp-app/res/values-ar/strings.xml index ce4d5ba5a..2dbd9bcb2 100644 --- a/opensrp-app/res/values-ar/strings.xml +++ b/opensrp-app/res/values-ar/strings.xml @@ -33,8 +33,8 @@ هل أنت متأكد أنك تريد تسجيل الخروج؟ سيتم مسح جميع البيانات استمارات غير متزامنة - التالي >> - >>سابق + التالية>> + >>السابقة صفحة {0} من {1} الاسم diff --git a/opensrp-app/res/values-fr/strings.xml b/opensrp-app/res/values-fr/strings.xml index 93d9865ef..c5898c615 100644 --- a/opensrp-app/res/values-fr/strings.xml +++ b/opensrp-app/res/values-fr/strings.xml @@ -33,8 +33,8 @@ Êtes-vous certain de vouloir se déconnecter? Tous les données seront effacées. Formulaires non-synchronisés - Prochain >> - << Dernier + Suivante >> + << Précédente Page {0} de {1} Nom diff --git a/opensrp-app/res/values/strings.xml b/opensrp-app/res/values/strings.xml index 4c1f9e74c..95b289392 100644 --- a/opensrp-app/res/values/strings.xml +++ b/opensrp-app/res/values/strings.xml @@ -33,8 +33,8 @@ Are you sure you want to log out? All the data will be cleared. Forms Unsynced - "Next >>" - "<< Previous" + Next >> + << Previous Page {0} of {1} NAME From 090bb32c53e407d6a68c72536bc19a1ee6dc690c Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Fri, 12 Jun 2020 12:09:59 +0300 Subject: [PATCH 30/70] Location picker show location tag - Add support for configurability of showing location tags alongside locations on picker tree - Documentation and tests --- README.md | 9 +++--- .../java/org/smartregister/AllConstants.java | 1 + .../location/helper/LocationHelper.java | 9 +++++- .../org/smartregister/util/AppProperties.java | 8 ++++++ .../java/org/smartregister/util/Utils.java | 3 +- .../location/helper/LocationHelperTest.java | 28 +++++++++++++++++-- 6 files changed, 49 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 830095add..6e1a95fa8 100644 --- a/README.md +++ b/README.md @@ -313,7 +313,8 @@ By placing a file named `app.properties` in your implementation assets folder (S ### Configurable Settings -| Configuration | Type | Default | Description | -| ----------------------------------- | ------- | ------- | ----------------------------------------------| -| `system.toaster.centered` | Boolean | false | Position toaster(s) at the center of the view(s) | -| `disable.location.picker.view` | Boolean | false | Disables LocationPicker View | \ No newline at end of file +| Configuration | Type | Default | Description | +| ----------------------------------- | ------- | -------- | -------------------------------------------------------------------------| +| `system.toaster.centered` | Boolean | false | Position toaster(s) at the center of the view(s) | +| `disable.location.picker.view` | Boolean | false | Disables LocationPicker View | +| `location.picker.tag.shown` | Boolean | false | Hides/Shows the location tag in the location picker tree view | diff --git a/opensrp-app/src/main/java/org/smartregister/AllConstants.java b/opensrp-app/src/main/java/org/smartregister/AllConstants.java index e0f4339b1..2a90b52b9 100644 --- a/opensrp-app/src/main/java/org/smartregister/AllConstants.java +++ b/opensrp-app/src/main/java/org/smartregister/AllConstants.java @@ -454,6 +454,7 @@ public class KEY { public static class PROPERTY { public static final String SYSTEM_TOASTER_CENTERED = "system.toaster.centered"; public static final String DISABLE_LOCATION_PICKER_VIEW = "disable.location.picker.view"; + public static final String LOCATION_PICKER_TAG_SHOWN = "location.picker.tag.shown"; } diff --git a/opensrp-app/src/main/java/org/smartregister/location/helper/LocationHelper.java b/opensrp-app/src/main/java/org/smartregister/location/helper/LocationHelper.java index 0e0839ec9..d9fec7290 100644 --- a/opensrp-app/src/main/java/org/smartregister/location/helper/LocationHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/location/helper/LocationHelper.java @@ -1,8 +1,10 @@ package org.smartregister.location.helper; +import android.support.annotation.VisibleForTesting; import android.util.Pair; import org.apache.commons.lang3.StringUtils; +import org.smartregister.AllConstants; import org.smartregister.CoreLibrary; import org.smartregister.domain.form.FormLocation; import org.smartregister.domain.jsonmapping.Location; @@ -471,7 +473,7 @@ private List getFormJsonData(TreeNode openMrsLoc formLocation.key = name; Set levels = node.getTags(); - formLocation.level = levels != null && !levels.isEmpty() ? levels.iterator().next() : ""; + formLocation.level = isLocationTagsShownEnabled() && levels != null && !levels.isEmpty() ? levels.iterator().next() : ""; LinkedHashMap> childMap = childMap(openMrsLocationData); @@ -511,6 +513,11 @@ private List getFormJsonData(TreeNode openMrsLoc return allLocationData; } + @VisibleForTesting + protected boolean isLocationTagsShownEnabled() { + return Utils.getBooleanProperty(AllConstants.PROPERTY.LOCATION_PICKER_TAG_SHOWN); + } + /** * This method sorts the options provided for a native form tree view question * diff --git a/opensrp-app/src/main/java/org/smartregister/util/AppProperties.java b/opensrp-app/src/main/java/org/smartregister/util/AppProperties.java index f4d343ae0..a6191091c 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/AppProperties.java +++ b/opensrp-app/src/main/java/org/smartregister/util/AppProperties.java @@ -26,4 +26,12 @@ public Boolean hasProperty(String key) { return getProperty(key) != null; } + + /** + * @param key key as present in the properties file + * @return returns true if a property with the provided key exists and the Boolean evaluation is true, false otherwise + */ + public Boolean isTrue(String key) { + return hasProperty(key) && getPropertyBoolean(key); + } } diff --git a/opensrp-app/src/main/java/org/smartregister/util/Utils.java b/opensrp-app/src/main/java/org/smartregister/util/Utils.java index 21dfce6a6..1d91a1ce9 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/Utils.java +++ b/opensrp-app/src/main/java/org/smartregister/util/Utils.java @@ -516,8 +516,7 @@ public static CommonPersonObjectClient convert(CommonPersonObject commonPersonOb public static boolean getBooleanProperty(String key) { - return CoreLibrary.getInstance().context().getAppProperties().hasProperty(key) ? CoreLibrary.getInstance().context().getAppProperties().getPropertyBoolean(key) : false; - + return CoreLibrary.getInstance().context().getAppProperties().isTrue(key); } public static void showToast(Context context, String message) { diff --git a/opensrp-app/src/test/java/org/smartregister/location/helper/LocationHelperTest.java b/opensrp-app/src/test/java/org/smartregister/location/helper/LocationHelperTest.java index a6e59847f..f84a8e48a 100644 --- a/opensrp-app/src/test/java/org/smartregister/location/helper/LocationHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/location/helper/LocationHelperTest.java @@ -299,7 +299,6 @@ public void testGenerateLocationHierarchyTreeWithMapShouldReturnListWithOtherFor } - @Test public void testGenerateLocationHierarchyTreeWithMapAndOtherOptionFalseShouldReturnEmptyList() { locationHelper = Mockito.spy(locationHelper); @@ -341,6 +340,32 @@ public void testGenerateLocationHierarchyTreeWithMapShouldReturnListWithZambiaFo assertEquals(1, formLocation.nodes.size()); } + @Test + public void testGenerateLocationHierarchyTreeWithMapShouldReturnListWithZambiaFormLocationAndLocationTags() { + locationHelper = Mockito.spy(locationHelper); + String locationData = "{\"locationsHierarchy\":{\"map\":{\"9c3e8715-1c59-44db-9709-2b49f440ef00\":{\"children\":{\"2e823ceb-4de6-41ac-8025-e2ae3512a331\":{\"children\":{\"620332e0-6108-4611-bac5-8b48d20051c9\":{\"children\":{\"ed7c4a07-6e02-4784-ae9a-9cd41cfef390\":{\"children\":{\"1b0ba804-54c3-40ef-820b-a8eaffa5d054\":{\"id\":\"1b0ba804-54c3-40ef-820b-a8eaffa5d054\",\"label\":\"ra_ksh_5\",\"node\":{\"locationId\":\"1b0ba804-54c3-40ef-820b-a8eaffa5d054\",\"name\":\"ra_ksh_5\",\"parentLocation\":{\"locationId\":\"ed7c4a07-6e02-4784-ae9a-9cd41cfef390\",\"name\":\"ra Kashikishi HAHC\",\"parentLocation\":{\"locationId\":\"620332e0-6108-4611-bac5-8b48d20051c9\",\"name\":\"ra Nchelenge\",\"serverVersion\":0,\"voided\":false},\"serverVersion\":0,\"voided\":false},\"tags\":[\"Operational Area\"],\"serverVersion\":0,\"voided\":false},\"parent\":\"ed7c4a07-6e02-4784-ae9a-9cd41cfef390\"}},\"id\":\"ed7c4a07-6e02-4784-ae9a-9cd41cfef390\",\"label\":\"ra Kashikishi HAHC\",\"node\":{\"locationId\":\"ed7c4a07-6e02-4784-ae9a-9cd41cfef390\",\"name\":\"ra Kashikishi HAHC\",\"parentLocation\":{\"locationId\":\"620332e0-6108-4611-bac5-8b48d20051c9\",\"name\":\"ra Nchelenge\",\"parentLocation\":{\"locationId\":\"2e823ceb-4de6-41ac-8025-e2ae3512a331\",\"name\":\"ra Luapula\",\"serverVersion\":0,\"voided\":false},\"serverVersion\":0,\"voided\":false},\"tags\":[\"Village\"],\"serverVersion\":0,\"voided\":false},\"parent\":\"620332e0-6108-4611-bac5-8b48d20051c9\"}},\"id\":\"620332e0-6108-4611-bac5-8b48d20051c9\",\"label\":\"ra Nchelenge\",\"node\":{\"locationId\":\"620332e0-6108-4611-bac5-8b48d20051c9\",\"name\":\"ra Nchelenge\",\"parentLocation\":{\"locationId\":\"2e823ceb-4de6-41ac-8025-e2ae3512a331\",\"name\":\"ra Luapula\",\"parentLocation\":{\"locationId\":\"9c3e8715-1c59-44db-9709-2b49f440ef00\",\"name\":\"ra Zambia\",\"serverVersion\":0,\"voided\":false},\"serverVersion\":0,\"voided\":false},\"tags\":[\"District\"],\"serverVersion\":0,\"voided\":false},\"parent\":\"2e823ceb-4de6-41ac-8025-e2ae3512a331\"}},\"id\":\"2e823ceb-4de6-41ac-8025-e2ae3512a331\",\"label\":\"ra Luapula\",\"node\":{\"locationId\":\"2e823ceb-4de6-41ac-8025-e2ae3512a331\",\"name\":\"ra Luapula\",\"parentLocation\":{\"locationId\":\"9c3e8715-1c59-44db-9709-2b49f440ef00\",\"name\":\"ra Zambia\",\"serverVersion\":0,\"voided\":false},\"tags\":[\"Province\"],\"serverVersion\":0,\"voided\":false},\"parent\":\"9c3e8715-1c59-44db-9709-2b49f440ef00\"}},\"id\":\"9c3e8715-1c59-44db-9709-2b49f440ef00\",\"label\":\"ra Zambia\",\"node\":{\"locationId\":\"9c3e8715-1c59-44db-9709-2b49f440ef00\",\"name\":\"ra Zambia\",\"tags\":[\"Country\"],\"serverVersion\":0,\"voided\":false}}},\"parentChildren\":{\"9c3e8715-1c59-44db-9709-2b49f440ef00\":[\"2e823ceb-4de6-41ac-8025-e2ae3512a331\"],\"ed7c4a07-6e02-4784-ae9a-9cd41cfef390\":[\"1b0ba804-54c3-40ef-820b-a8eaffa5d054\"],\"620332e0-6108-4611-bac5-8b48d20051c9\":[\"ed7c4a07-6e02-4784-ae9a-9cd41cfef390\"],\"2e823ceb-4de6-41ac-8025-e2ae3512a331\":[\"620332e0-6108-4611-bac5-8b48d20051c9\"]}}}"; + + ArrayList allowedLevels = new ArrayList<>(); + allowedLevels.add("Country"); + allowedLevels.add("Province"); + allowedLevels.add("Region"); + allowedLevels.add("District"); + allowedLevels.add("Sub-district"); + allowedLevels.add("Operational Area"); + + LinkedHashMap> map = AssetHandler.jsonStringToJava(locationData, LocationTree.class).getLocationsHierarchy(); + + Mockito.doReturn(true).when(locationHelper).isLocationTagsShownEnabled(); + List formLocationsList = locationHelper.generateLocationHierarchyTree(false, allowedLevels, map); + + assertEquals(1, formLocationsList.size()); + + FormLocation formLocation = formLocationsList.get(0); + assertEquals("Zambia", formLocation.name); + assertEquals("ra Zambia", formLocation.key); + assertEquals("Country", formLocation.level); + assertEquals(1, formLocation.nodes.size()); + } @Test public void testGenerateLocationHierarchyTreeShouldReturnListWithOtherFormLocationOnly() { @@ -363,7 +388,6 @@ public void testGenerateLocationHierarchyTreeShouldReturnListWithOtherFormLocati } - @Test public void testGetOpenMrsReadableName() { assertEquals("Zambia", locationHelper.getOpenMrsReadableName("ra Zambia")); From b6b98cbb85699f2f65619a207b9c612230115714 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Sat, 13 Jun 2020 16:04:55 +0300 Subject: [PATCH 31/70] reset android manifest config --- sample/src/main/AndroidManifest.xml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index cdf071f18..b10bfcf21 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -16,22 +16,20 @@ android:theme="@style/AppTheme" tools:replace="android:theme"> - + android:name=".MainActivity" + android:label="@string/app_name" + android:theme="@style/AppTheme.NoActionBar"> - - +
From 8bbf545e7a0ec711a3206006ae4b9146a42da51b Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Sat, 13 Jun 2020 16:03:34 +0300 Subject: [PATCH 32/70] Fix clear app settings bug - Fix app crashes on login after app manually reset --- .../java/org/smartregister/account/AccountHelper.java | 2 +- .../org/smartregister/login/task/RemoteLoginTask.java | 1 + .../java/org/smartregister/security/SecurityHelper.java | 9 ++++++--- .../main/java/org/smartregister/service/UserService.java | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java index aad68d1c1..3aca9500c 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java @@ -23,7 +23,7 @@ public static final class CONFIGURATION_CONSTANTS { public final static String TOKEN_ENDPOINT_URL = "token_endpoint_url"; public final static String AUTHORIZATION_ENDPOINT_URL = "authorization_endpoint_url"; public final static String ISSUER_ENDPOINT_URL = "issuer_endpoint_url"; - public static final String USERINFO_ENDPOINT_URL = "userinfo_endpint_url"; + public static final String USERINFO_ENDPOINT_URL = "userinfo_endpoint_url"; } public static final class OAUTH { diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index 2dcbae997..8f679db33 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -103,6 +103,7 @@ protected LoginResponse doInBackground(Void... params) { mAccountManager.addAccountExplicitly(account, response.getRefreshToken(), userData); mAccountManager.setAuthToken(account, mLoginView.getAuthTokenType(), response.getAccessToken()); mAccountManager.setPassword(account, response.getRefreshToken()); + mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID)); if (getOpenSRPContext().userService().getGroupId(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { diff --git a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java index 91b4f3d7a..4927f0553 100644 --- a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java @@ -37,8 +37,9 @@ public static char[] readValue(Editable editable) { * @param array character array */ public static void clearArray(byte[] array) { - - Arrays.fill(array, (byte) 0); + if (array != null) { + Arrays.fill(array, (byte) 0); + } } /** @@ -47,7 +48,9 @@ public static void clearArray(byte[] array) { * @param array character array */ public static void clearArray(char[] array) { - Arrays.fill(array, '*'); + if (array != null) { + Arrays.fill(array, '*'); + } } /** diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 06e700204..71d0387b5 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -330,8 +330,8 @@ private boolean loginWith(String userName, char[] password) { try { KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); if (privateKeyEntry != null) { - byte[] groupId = Base64.decode(encryptedGroupId, Base64.DEFAULT); - setupContextForLogin(SecurityHelper.toChars(decryptString(privateKeyEntry, encryptedGroupId))); + byte[] groupId = decryptString(privateKeyEntry, encryptedGroupId); + setupContextForLogin(SecurityHelper.toChars(groupId)); SecurityHelper.clearArray(groupId); } } catch (Exception e) { From 56b13f8fa40a83973093d99d2cbdf725ac111370 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Mon, 15 Jun 2020 09:24:45 +0300 Subject: [PATCH 33/70] Update Manifest configuration - Add explicit permissions for declared components --- opensrp-app/AndroidManifest.xml | 45 +++++++++++++++++++++++++----- opensrp-app/res/values/strings.xml | 9 ++++++ 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/opensrp-app/AndroidManifest.xml b/opensrp-app/AndroidManifest.xml index 168741189..27f14f738 100644 --- a/opensrp-app/AndroidManifest.xml +++ b/opensrp-app/AndroidManifest.xml @@ -31,9 +31,14 @@ android:largeHeap="true"> + android:description="@string/component_desc_image_upload_service" + android:enabled="true" + android:exported="false" /> - + @@ -44,74 +49,100 @@ - - + + + - + + - + - + Do you want to clear data to login with a different team/location You are trying to login with a user in a different team/location. Upload of pending data and clearing of data for user %s in Team %s is required. Do you want to continue? Replication error occurred + Form Update + This form has content updates + (Current Corrupted Form) + Select a rollback form + You cannot select this form because it\'s corrupted! + Form has been successfully selected! Reopen this form for the changes to take effect + Uploads images to the OpenSRP server + Manages user authentication and authorization + Processes the records when using peer to peer for file sharing From 1d4e6e43c0c024878288ff66240a3c3ce0d4e173 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Mon, 15 Jun 2020 09:29:47 +0300 Subject: [PATCH 34/70] Fix build error - Remove multidex declaration --- opensrp-app/build.gradle | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/opensrp-app/build.gradle b/opensrp-app/build.gradle index 7da0d4b5f..1d43df1f3 100644 --- a/opensrp-app/build.gradle +++ b/opensrp-app/build.gradle @@ -208,6 +208,20 @@ dependencies { implementation 'org.smartregister:opensrp-plan-evaluator:0.0.19-SNAPSHOT' implementation 'xerces:xercesImpl:2.12.0' + implementation('org.smartregister:opensrp-client-native-form:1.9.2-SNAPSHOT@aar') { + transitive = true + exclude group: 'id.zelory', module: 'compressor' + exclude group: 'com.android.support', module: 'recyclerview-v7' + exclude group: 'com.android.support', module: 'appcompat-v7' + exclude group: 'com.android.support', module: 'cardview-v7' + exclude group: 'com.android.support', module: 'support-media-compat' + exclude group: 'com.android.support', module: 'support-v4' + exclude group: 'com.android.support', module: 'design' + exclude group: 'org.yaml', module: 'snakeyaml' + exclude group: 'io.ona.rdt-capture', module: 'lib' + exclude group: 'com.github.johnkil.print', module: 'print' + exclude group: 'com.android.support', module: 'multidex' + } } dependencies { @@ -215,6 +229,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') androidTestImplementation 'junit:junit:4.12' + testImplementation 'com.android.support:multidex:1.0.0' testImplementation group: 'com.google.android', name: 'android-test', version: '4.1.1.4' testImplementation 'org.apache.maven:maven-ant-tasks:2.1.3' testImplementation 'org.mockito:mockito-core:1.9.5' From cd93e8acff40c94c78b6e72c41eda5566b4f0879 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Mon, 15 Jun 2020 14:12:27 +0300 Subject: [PATCH 35/70] Support for local Multi-tenancy - Refactor offline multi tenancy implementation for same Team Members - Adds support for No need to remotely authenticate if all team members had logged in device previously --- .../smartregister/account/AccountHelper.java | 56 +++++++++++++++---- .../login/interactor/BaseLoginInteractor.java | 6 +- .../repository/AllSharedPreferences.java | 20 +++++-- .../org/smartregister/service/HTTPAgent.java | 6 +- .../smartregister/service/UserService.java | 25 +++++---- .../util/OpenSRPImageLoader.java | 2 +- .../account/AccountHelperTest.java | 8 +-- .../repository/AllSharedPreferencesTest.java | 9 +-- .../smartregister/service/HTTPAgentTest.java | 10 ++-- 9 files changed, 96 insertions(+), 46 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java index 3aca9500c..5afb39d36 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java @@ -54,18 +54,46 @@ public static final class TOKEN_TYPE { public final static String ADMIN = "admin"; } + /** + * Gets OAuth Account by the account name and account type + * + * @param accountName name of account within account manage + * @param accountType unique name to identify our account type in the Account Manager + * @return Account retrieved + */ + public static Account getOauthAccountByNameAndType(String accountName, String accountType) { - public static Account getOauthAccountByType(String accountType) { Account[] accounts = accountManager.getAccountsByType(accountType); - if (accounts.length > 0) { - Account account = accounts[0]; - return account; + return accounts.length > 0 ? selectAccount(accounts, accountName) : null; + } + + /** + * Get specified Account by name from the list returned by the account manager + * + * @param accountName name of account within account manage + * @param accounts list of Accounts returned by the Account Manager + * @return Account selected from list + */ + public static Account selectAccount(Account[] accounts, String accountName) { + for (Account account : accounts) { + if (accountName.equals(account.name)) { + return account; + } } + return null; } - public static String getAccountManagerValue(String key, String accountType) { - Account account = AccountHelper.getOauthAccountByType(accountType); + /** + * Gets user data value with the specified key from the Account Manager + * + * @param key of the user data value we want to retrieve + * @param accountType unique name to identify our account type in the Account Manager + * @param accountName name of account within account manage + * @return access token + */ + public static String getAccountManagerValue(String key, String accountName, String accountType) { + Account account = AccountHelper.getOauthAccountByNameAndType(accountName, accountType); if (account != null) { return accountManager.getUserData(account, key); } @@ -73,12 +101,15 @@ public static String getAccountManagerValue(String key, String accountType) { } /** + * Gets OAuth Token + * + * @param accountName name of account within account manage * @param accountType unique name to identify our account type in the Account Manager * @param authTokenType type of token requested from server e.g. PROVIDER, ADMIN * @return access token */ - public static String getOAuthToken(String accountType, String authTokenType) { - Account account = getOauthAccountByType(accountType); + public static String getOAuthToken(String accountName, String accountType, String authTokenType) { + Account account = getOauthAccountByNameAndType(accountName, accountType); try { return accountManager.blockingGetAuthToken(account, authTokenType, true); @@ -88,7 +119,9 @@ public static String getOAuthToken(String accountType, String authTokenType) { } } - /** This method invalidates the auth token so that the Authenticator can fetch a new one from server + /** + * This method invalidates the auth token so that the Authenticator can fetch a new one from server + * * @param accountType unique name to identify our account type in the Account Manager * @param authToken token to invalidate */ @@ -98,12 +131,13 @@ public static void invalidateAuthToken(String accountType, String authToken) { /** + * @param accountName name of account within account manage * @param accountType unique name to identify our account type in the Account Manager * @param authTokenType type of token requested from server e.g. PROVIDER, ADMIN * @return access token cached */ - public static String getCachedOAuthToken(String accountType, String authTokenType) { - Account account = getOauthAccountByType(accountType); + public static String getCachedOAuthToken(String accountName, String accountType, String authTokenType) { + Account account = getOauthAccountByNameAndType(accountName, accountType); return accountManager.peekAuthToken(account, authTokenType); } diff --git a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java index 4c64006eb..a4c06ba20 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java +++ b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java @@ -62,8 +62,10 @@ public void onDestroy(boolean isChangingConfiguration) { @Override public void login(WeakReference view, String userName, char[] password) { - loginWithLocalFlag(view, !getSharedPreferences().fetchForceRemoteLogin() - && userName.equalsIgnoreCase(getSharedPreferences().fetchRegisteredANM()), userName, password); + + boolean localLogin = !getSharedPreferences().fetchForceRemoteLogin(userName); + loginWithLocalFlag(view, localLogin && getSharedPreferences().isRegisteredANM(userName), userName, password); + } public void loginWithLocalFlag(WeakReference view, boolean localLogin, String userName, char[] password) { diff --git a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java index 8c374f56c..284d8e468 100644 --- a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java @@ -8,6 +8,8 @@ import java.net.MalformedURLException; import java.net.URL; +import java.util.HashSet; +import java.util.Set; import static org.smartregister.AllConstants.CURRENT_LOCALITY; import static org.smartregister.AllConstants.DEFAULT_LOCALE; @@ -27,6 +29,7 @@ public class AllSharedPreferences { public static final String ANM_IDENTIFIER_PREFERENCE_KEY = "anmIdentifier"; + public static final String ANM_IDENTIFIER_SET_PREFERENCE_KEY = "anmIdentifierSet"; private static final String HOST = "HOST"; private static final String PORT = "PORT"; private static final String LAST_SYNC_DATE = "LAST_SYNC_DATE"; @@ -49,18 +52,26 @@ public AllSharedPreferences(SharedPreferences preferences) { public void updateANMUserName(String userName) { preferences.edit().putString(ANM_IDENTIFIER_PREFERENCE_KEY, userName).commit(); + + Set anmIdentifiers = new HashSet<>(preferences.getStringSet(ANM_IDENTIFIER_SET_PREFERENCE_KEY, new HashSet<>())); + anmIdentifiers.add(userName); + preferences.edit().putStringSet(ANM_IDENTIFIER_SET_PREFERENCE_KEY, anmIdentifiers).commit(); } public String fetchRegisteredANM() { return preferences.getString(ANM_IDENTIFIER_PREFERENCE_KEY, "").trim(); } - public boolean fetchForceRemoteLogin() { - return preferences.getBoolean(FORCE_REMOTE_LOGIN, true); + public boolean isRegisteredANM(String userName) { + return preferences.getStringSet(ANM_IDENTIFIER_SET_PREFERENCE_KEY, new HashSet<>()).contains(userName); + } + + public boolean fetchForceRemoteLogin(String username) { + return preferences.getBoolean(new StringBuffer(FORCE_REMOTE_LOGIN).append('_').append(username).toString(), true); } - public void saveForceRemoteLogin(boolean forceRemoteLogin) { - preferences.edit().putBoolean(FORCE_REMOTE_LOGIN, forceRemoteLogin).commit(); + public void saveForceRemoteLogin(boolean forceRemoteLogin, String username) { + preferences.edit().putBoolean(new StringBuffer(FORCE_REMOTE_LOGIN).append('_').append(username).toString(), forceRemoteLogin).commit(); } public String fetchServerTimeZone() { @@ -336,5 +347,6 @@ public String fetchFormsVersion() { public SharedPreferences getPreferences() { return preferences; } + } diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index a3db60634..1f8569af7 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -127,8 +127,8 @@ private HttpURLConnection initializeHttp(String requestURLPath, boolean setOauth urlConnection.setReadTimeout(getReadTimeout()); if (setOauthToken) { AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); - if (AccountHelper.getOauthAccountByType(authenticatorXml.getAccountType()) != null) - urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, new StringBuilder(AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " ").append(AccountHelper.getOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); + if (AccountHelper.getOauthAccountByNameAndType(allSharedPreferences.fetchRegisteredANM(), authenticatorXml.getAccountType()) != null) + urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, new StringBuilder(AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " ").append(AccountHelper.getOAuthToken(allSharedPreferences.fetchRegisteredANM(), authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).toString()); } return urlConnection; } @@ -163,7 +163,7 @@ public Response fetch(String requestURLPath) { public void invalidateExpiredCachedAccessToken() { AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); - String authToken = AccountHelper.getCachedOAuthToken(authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + String authToken = AccountHelper.getCachedOAuthToken(allSharedPreferences.fetchRegisteredANM(), authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); if (authToken != null) AccountHelper.invalidateAuthToken(authenticatorXml.getAccountType(), authToken); } diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 71d0387b5..c49400f2b 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -168,7 +168,7 @@ public TimeStatus validateStoredServerTimeZone() { } if (!result.equals(TimeStatus.OK)) { - forceRemoteLogin(); + forceRemoteLogin(allSharedPreferences.fetchRegisteredANM()); } return result; @@ -209,12 +209,12 @@ public TimeStatus validateDeviceTime(LoginResponseData userInfo, long serverTime public boolean isValidLocalLogin(String userName, char[] password) { return allSharedPreferences.fetchRegisteredANM().equals(userName) && DrishtiApplication.getInstance().getRepository() - .canUseThisPassword(password) && !allSharedPreferences.fetchForceRemoteLogin(); + .canUseThisPassword(password) && !allSharedPreferences.fetchForceRemoteLogin(userName); } public boolean isUserInValidGroup(final String userName, final char[] password) { // Check if everything OK for local login - if (keyStore != null && userName != null && password != null && !allSharedPreferences.fetchForceRemoteLogin()) { + if (keyStore != null && userName != null && password != null && !allSharedPreferences.fetchForceRemoteLogin(userName)) { String username = userName.equalsIgnoreCase(allSharedPreferences.fetchRegisteredANM()) ? allSharedPreferences.fetchRegisteredANM() : userName; try { KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(username); @@ -251,7 +251,7 @@ public char[] getGroupId(String userName) { public char[] getGroupId(String userName, KeyStore.PrivateKeyEntry privateKeyEntry) { if (privateKeyEntry != null) { - String encryptedGroupId = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); + String encryptedGroupId = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); if (encryptedGroupId != null) { try { @@ -323,14 +323,14 @@ public Response getLocationInformation() { private boolean loginWith(String userName, char[] password) { boolean loginSuccessful = true; - if (usesGroupIdAsDBPassword()) { + if (usesGroupIdAsDBPassword(userName)) { - String encryptedGroupId = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); + String encryptedGroupId = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); try { KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); if (privateKeyEntry != null) { - byte[] groupId = decryptString(privateKeyEntry, encryptedGroupId); + byte[] groupId = decryptString(privateKeyEntry, encryptedGroupId); setupContextForLogin(SecurityHelper.toChars(groupId)); SecurityHelper.clearArray(groupId); } @@ -353,10 +353,11 @@ private boolean loginWith(String userName, char[] password) { /** * Checks whether to use the groupId for the current user to decrypt the database * + * @param username the username * @return TRUE if the user decrypts the database using the groupId */ - private boolean usesGroupIdAsDBPassword() { - return AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()) != null; + private boolean usesGroupIdAsDBPassword(String username) { + return AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, username, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()) != null; } public void localLogin(String userName, char[] password) { @@ -384,11 +385,11 @@ public void processLoginResponseDataForUser(String userName, char[] password, Lo StringUtils.isNotBlank(allSharedPreferences.fetchDefaultTeamId(username))) && (getUserLocation(userInfo) != null || StringUtils.isNotBlank(allSettings.fetchANMLocation()))) - allSharedPreferences.saveForceRemoteLogin(false); + allSharedPreferences.saveForceRemoteLogin(false, username); } - public void forceRemoteLogin() { - allSharedPreferences.saveForceRemoteLogin(true); + public void forceRemoteLogin(String userName) { + allSharedPreferences.saveForceRemoteLogin(true, userName); } public User getUserData(LoginResponseData userInfo) { diff --git a/opensrp-app/src/main/java/org/smartregister/util/OpenSRPImageLoader.java b/opensrp-app/src/main/java/org/smartregister/util/OpenSRPImageLoader.java index 4a56c87e9..6375a4059 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/OpenSRPImageLoader.java +++ b/opensrp-app/src/main/java/org/smartregister/util/OpenSRPImageLoader.java @@ -186,7 +186,7 @@ public HttpResponse performRequest(Request request, Map heade } private static void addBearerTokenAuthorizationHeader(Map headers) { - String accessToken = AccountHelper.getOAuthToken(CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + String accessToken = AccountHelper.getOAuthToken(CoreLibrary.getInstance().context().allSharedPreferences().fetchRegisteredANM(), CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); headers.put(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, new StringBuilder(AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BEARER + " ").append(accessToken).toString()); } diff --git a/opensrp-app/src/test/java/org/smartregister/account/AccountHelperTest.java b/opensrp-app/src/test/java/org/smartregister/account/AccountHelperTest.java index 307e058dd..0a6417d15 100644 --- a/opensrp-app/src/test/java/org/smartregister/account/AccountHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/account/AccountHelperTest.java @@ -45,7 +45,7 @@ public void setUp() throws Exception { @Test public void testGetOauthAccountByType() { - Account account = AccountHelper.getOauthAccountByType(CORE_ACCOUNT_TYPE); + Account account = AccountHelper.getOauthAccountByNameAndType(CORE_ACCOUNT_NAME, CORE_ACCOUNT_TYPE); Assert.assertNotNull(account); Assert.assertEquals(CORE_ACCOUNT_NAME, account.name); } @@ -57,7 +57,7 @@ public void testGetAccountManagerValue() { Mockito.doReturn(TEST_VALUE).when(accountManager).getUserData(ArgumentMatchers.any(Account.class), ArgumentMatchers.eq(TEST_KEY)); - String value = AccountHelper.getAccountManagerValue(TEST_KEY, CORE_ACCOUNT_TYPE); + String value = AccountHelper.getAccountManagerValue(TEST_KEY, CORE_ACCOUNT_NAME, CORE_ACCOUNT_TYPE); Assert.assertNotNull(value); Assert.assertEquals(TEST_VALUE, value); } @@ -66,7 +66,7 @@ public void testGetAccountManagerValue() { public void testGetOAuthToken() throws AuthenticatorException, OperationCanceledException, IOException { Mockito.doReturn(TEST_TOKEN_VALUE).when(accountManager).blockingGetAuthToken(new Account(CORE_ACCOUNT_NAME, CORE_ACCOUNT_TYPE), AUTH_TOKEN_TYPE, true); - String myToken = AccountHelper.getOAuthToken(CORE_ACCOUNT_TYPE, AUTH_TOKEN_TYPE); + String myToken = AccountHelper.getOAuthToken(CORE_ACCOUNT_NAME, CORE_ACCOUNT_TYPE, AUTH_TOKEN_TYPE); Assert.assertNotNull(myToken); Assert.assertEquals(TEST_TOKEN_VALUE, myToken); } @@ -84,7 +84,7 @@ public void testGetCachedOAuthToken() { Account account = new Account(CORE_ACCOUNT_NAME, CORE_ACCOUNT_TYPE); Mockito.doReturn(TEST_TOKEN_VALUE).when(accountManager).peekAuthToken(account, AUTH_TOKEN_TYPE); - String cachedAuthToken = AccountHelper.getCachedOAuthToken(CORE_ACCOUNT_TYPE, AUTH_TOKEN_TYPE); + String cachedAuthToken = AccountHelper.getCachedOAuthToken(CORE_ACCOUNT_NAME, CORE_ACCOUNT_TYPE, AUTH_TOKEN_TYPE); Mockito.verify(accountManager).peekAuthToken(account, AUTH_TOKEN_TYPE); Assert.assertEquals(TEST_TOKEN_VALUE, cachedAuthToken); diff --git a/opensrp-app/src/test/java/org/smartregister/repository/AllSharedPreferencesTest.java b/opensrp-app/src/test/java/org/smartregister/repository/AllSharedPreferencesTest.java index 1807a988f..b95139098 100644 --- a/opensrp-app/src/test/java/org/smartregister/repository/AllSharedPreferencesTest.java +++ b/opensrp-app/src/test/java/org/smartregister/repository/AllSharedPreferencesTest.java @@ -30,6 +30,7 @@ public class AllSharedPreferencesTest extends TestCase { private SharedPreferences preferences; private static final String HOST = "HOST"; private static final String PORT = "PORT"; + private static final String USERNAME = "USERNAME"; AllSharedPreferences allSharedPreferences; private final String str = "default"; @@ -54,13 +55,13 @@ public void tearDown() { @Test public void assertupdateANMUserNameCallsPreferenceEdit() { allSharedPreferences.updateANMUserName(""); - Mockito.verify(preferences, Mockito.times(1)).edit(); + Mockito.verify(preferences, Mockito.times(2)).edit(); } @Test public void assertFetchForceRemoteLogin() { - Mockito.when(preferences.getBoolean(AllConstants.FORCE_REMOTE_LOGIN, true)).thenReturn(true); - Assert.assertEquals(allSharedPreferences.fetchForceRemoteLogin(), true); + Mockito.when(preferences.getBoolean(AllConstants.FORCE_REMOTE_LOGIN + "_" + USERNAME, true)).thenReturn(true); + Assert.assertEquals(allSharedPreferences.fetchForceRemoteLogin(USERNAME), true); } @Test @@ -168,7 +169,7 @@ public void assertFetchDefaultLocalityId() { @Test public void assertSaveForceRemoteLogin() { - allSharedPreferences.saveForceRemoteLogin(true); + allSharedPreferences.saveForceRemoteLogin(true, USERNAME); Mockito.verify(preferences, Mockito.times(1)).edit(); } diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index 3a8f0ad83..87c46c40b 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -176,8 +176,8 @@ public void setUp() { Mockito.doReturn(1).when(syncConfiguration).getMaxAuthenticationRetries(); PowerMockito.mockStatic(AccountHelper.class); - PowerMockito.when(AccountHelper.getOauthAccountByType(accountAuthenticatorXml.getAccountType())).thenReturn(account); - PowerMockito.when(AccountHelper.getOAuthToken(accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); + PowerMockito.when(AccountHelper.getOauthAccountByNameAndType(TEST_USERNAME, accountAuthenticatorXml.getAccountType())).thenReturn(account); + PowerMockito.when(AccountHelper.getOAuthToken(TEST_USERNAME, accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); httpAgent = new HTTPAgent(context, allSharedPreferences, dristhiConfiguration); httpAgent.setConnectTimeout(60000); @@ -509,7 +509,7 @@ public void testFetchInvalidatesCacheIfUnauthorizedAndReturnsCorrectResponse() t PowerMockito.when(IOUtils.toString(errorStream)).thenReturn(FETCH_DATA_REQUEST_SERVER_RESPONSE); PowerMockito.mockStatic(AccountHelper.class); - PowerMockito.when(AccountHelper.getCachedOAuthToken(accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); + PowerMockito.when(AccountHelper.getCachedOAuthToken(TEST_USERNAME, accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); Response response = httpAgentSpy.fetch(SECURE_RESOURCE_ENDPOINT); Assert.assertNotNull(response); @@ -534,7 +534,7 @@ public void testPostInvokesInvalidateCacheIfUnauthorizedOnFirstAttempt() throws PowerMockito.when(IOUtils.toString(errorStream)).thenReturn(FETCH_DATA_REQUEST_SERVER_RESPONSE); PowerMockito.mockStatic(AccountHelper.class); - PowerMockito.when(AccountHelper.getCachedOAuthToken(accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); + PowerMockito.when(AccountHelper.getCachedOAuthToken(TEST_USERNAME, accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); Mockito.doReturn(httpURLConnection).when(httpAgentSpy).generatePostRequest(SECURE_RESOURCE_ENDPOINT, SAMPLE_POST_REQUEST_PAYLOAD); @@ -557,7 +557,7 @@ public void testFetchWithCredentialsInvokesInvalidateCacheIfUnauthorizedOnFirstA PowerMockito.when(IOUtils.toString(errorStream)).thenReturn(FETCH_DATA_REQUEST_SERVER_RESPONSE); PowerMockito.mockStatic(AccountHelper.class); - PowerMockito.when(AccountHelper.getCachedOAuthToken(accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); + PowerMockito.when(AccountHelper.getCachedOAuthToken(TEST_USERNAME, accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); Response response = httpAgentSpy.fetchWithCredentials(SECURE_RESOURCE_ENDPOINT, SAMPLE_TEST_TOKEN); Assert.assertNotNull(response); From 75cded41ec3d2589fe0be69ed1caf434526ebcf4 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Mon, 15 Jun 2020 14:23:32 +0300 Subject: [PATCH 36/70] Refactor forced logout re-authentication - Refactor Re-Authentication to use Account manager - For clean up/Maintainalibility --- .../account/AccountAuthenticator.java | 12 +++++-- .../smartregister/account/AccountHelper.java | 17 +++++++++- .../smartregister/account/AccountService.java | 9 +++-- .../login/task/RemoteLoginTask.java | 4 +-- .../helper/SyncSettingsServiceHelper.java | 32 +++++------------ .../org/smartregister/util/SyncUtils.java | 34 +++++++++++++++---- .../account/AccountHelperTest.java | 14 ++++++++ .../helper/SyncSettingsServiceHelperTest.java | 2 +- 8 files changed, 86 insertions(+), 38 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java index 0c2a75bdb..92d3a9389 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java @@ -111,11 +111,19 @@ public Bundle editProperties(AccountAuthenticatorResponse response, String accou @Override public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { - return null; + return options; } @Override public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { - return null; + + final Intent intent = new Intent(mContext, CoreLibrary.getInstance().getSyncConfiguration().getAuthenticationActivity()); + intent.putExtra(AccountHelper.INTENT_KEY.AUTH_TYPE, authTokenType); + intent.putExtra(AccountHelper.INTENT_KEY.IS_NEW_ACCOUNT, false); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; } } diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java index 5afb39d36..91d757993 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java @@ -2,6 +2,8 @@ import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; +import android.os.Bundle; import org.smartregister.CoreLibrary; @@ -14,7 +16,6 @@ public class AccountHelper { private static AccountManager accountManager = CoreLibrary.getInstance().getAccountManager(); - public final static String KEY_REFRESH_TOKEN = "KEY_REFRESH_TOKEN"; public final static int MAX_AUTH_RETRIES = 1; @@ -141,5 +142,19 @@ public static String getCachedOAuthToken(String accountName, String accountType, return accountManager.peekAuthToken(account, authTokenType); } + /** + * Prompt the user to re-authenticate + * + * @param accountName name of account within account manage + * @param accountType unique name to identify our account type in the Account Manager + * @param authTokenType type of token requested from server e.g. PROVIDER, ADMIN + * @return access token + */ + public static AccountManagerFuture reAuthenticateUserAfterSessionExpired(String accountName, String accountType, String authTokenType) { + Account account = getOauthAccountByNameAndType(accountName, accountType); + return accountManager.updateCredentials(account, authTokenType, null, null, null, null); + + + } } diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountService.java b/opensrp-app/src/main/java/org/smartregister/account/AccountService.java index 2056fc9c8..51da428bf 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountService.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountService.java @@ -9,10 +9,15 @@ */ public class AccountService extends Service { + private AccountAuthenticator authenticator; + @Override - public IBinder onBind(Intent intent) { + public void onCreate() { + authenticator = new AccountAuthenticator(this); + } - AccountAuthenticator authenticator = new AccountAuthenticator(this); + @Override + public IBinder onBind(Intent intent) { return authenticator.getIBinder(); } diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index 8f679db33..c86268f6a 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -159,10 +159,10 @@ public static Context getOpenSRPContext() { return CoreLibrary.getInstance().context(); } - protected JSONArray pullSetting(SyncSettingsServiceHelper syncSettingsServiceHelper, LoginResponse loginResponse) { + protected JSONArray pullSetting(SyncSettingsServiceHelper syncSettingsServiceHelper, LoginResponse loginResponse, String accessToken) { JSONArray settings = new JSONArray(); try { - settings = syncSettingsServiceHelper.pullSettingsFromServer(Utils.getFilterValue(loginResponse, CoreLibrary.getInstance().getSyncConfiguration().getSyncFilterParam())); + settings = syncSettingsServiceHelper.pullSettingsFromServer(Utils.getFilterValue(loginResponse, CoreLibrary.getInstance().getSyncConfiguration().getSyncFilterParam()), accessToken); } catch (JSONException e) { Timber.e(e); } diff --git a/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java b/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java index 0c52b8535..e802bcb75 100644 --- a/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java @@ -74,8 +74,8 @@ private JSONArray getSettings() throws JSONException { String authToken = AccountHelper.getCachedOAuthToken(sharedPreferences.fetchRegisteredANM(), authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); JSONArray settings = pullSettingsFromServer(getInstance().getSyncConfiguration().getSettingsSyncFilterValue(), authToken); - getGlobalSettings(settings); - getExtraSettings(settings); + getGlobalSettings(settings, authToken); + getExtraSettings(settings, authToken); return settings; } @@ -85,14 +85,14 @@ protected CoreLibrary getInstance() { } // will automatically use the resolve check - private void getExtraSettings(JSONArray settings) throws JSONException { + private void getExtraSettings(JSONArray settings, String accessToken) throws JSONException { JSONArray completeExtraSettings = new JSONArray(); if (getInstance().getSyncConfiguration().hasExtraSettingsSync()) { List syncParams = getInstance().getSyncConfiguration().getExtraSettingsParameters(); if (syncParams.size() > 0) { for (String params : syncParams) { String url = SettingsSyncIntentService.SETTINGS_URL + "?" + params + "&" + AllConstants.SERVER_VERSION + "=" + sharedPreferences.fetchLastSettingsSyncTimeStamp() + "&" + AllConstants.RESOLVE + "=" + getInstance().getSyncConfiguration().resolveSettings(); - JSONArray extraSettings = pullSettings(url); + JSONArray extraSettings = pullSettings(url, accessToken); if (extraSettings != null) { aggregateSettings(completeExtraSettings, extraSettings); } @@ -103,10 +103,10 @@ private void getExtraSettings(JSONArray settings) throws JSONException { } } - private void getGlobalSettings(JSONArray settings) throws JSONException { + private void getGlobalSettings(JSONArray settings, String accessToken) throws JSONException { JSONArray globalSettings = new JSONArray(); if (getInstance().getSyncConfiguration().hasGlobalSettings()) { - globalSettings = pullGlobalSettingsFromServer(); + globalSettings = pullGlobalSettingsFromServer(accessToken); } aggregateSettings(settings, globalSettings); @@ -170,9 +170,9 @@ protected SyncFilter getSettingsSyncFilterParam() { * @return settings {@link JSONArray} -- a JSON array of all the settings * @throws JSONException */ - public JSONArray pullGlobalSettingsFromServer() throws JSONException { + public JSONArray pullGlobalSettingsFromServer(String accessToken) throws JSONException { String url = SettingsSyncIntentService.SETTINGS_URL + "?" + AllConstants.SERVER_VERSION + "=" + sharedPreferences.fetchLastSettingsSyncTimeStamp(); - return pullSettings(url); + return pullSettings(url, accessToken); } @@ -219,20 +219,4 @@ private JSONArray pullSettings(String directoryUrl, String accessToken) throws J protected Response getResponse(String completeUrl, String accessToken) { return httpAgent.fetchWithCredentials(completeUrl, accessToken); } - - public String getUsername() { - return username != null ? username : sharedPreferences.fetchRegisteredANM(); - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password != null ? password : getInstance().context().allSettings().fetchANMPassword(); - } - - public void setPassword(String password) { - this.password = password; - } } diff --git a/opensrp-app/src/main/java/org/smartregister/util/SyncUtils.java b/opensrp-app/src/main/java/org/smartregister/util/SyncUtils.java index 5a31f61c4..5d34dd85a 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/SyncUtils.java +++ b/opensrp-app/src/main/java/org/smartregister/util/SyncUtils.java @@ -1,9 +1,15 @@ package org.smartregister.util; +import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.support.annotation.NonNull; import org.apache.commons.lang3.StringUtils; import org.json.JSONArray; @@ -11,10 +17,12 @@ import org.json.JSONObject; import org.smartregister.CoreLibrary; import org.smartregister.R; +import org.smartregister.account.AccountHelper; import org.smartregister.domain.Setting; import org.smartregister.repository.AllSettings; import org.smartregister.repository.BaseRepository; +import java.io.IOException; import java.util.List; import timber.log.Timber; @@ -44,12 +52,27 @@ public boolean verifyAuthorization() { return CoreLibrary.getInstance().context().getHttpAgent().verifyAuthorization(); } - public void logoutUser() { + public void logoutUser() throws AuthenticatorException, OperationCanceledException, IOException { //force remote login - opensrpContent.userService().forceRemoteLogin(); + opensrpContent.userService().forceRemoteLogin(opensrpContent.allSharedPreferences().fetchRegisteredANM()); + + Intent logoutUserIntent = getLogoutUserIntent(); + + AccountManagerFuture reAuthenticateFuture = AccountHelper.reAuthenticateUserAfterSessionExpired(opensrpContent.allSharedPreferences().fetchRegisteredANM(), CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + Intent accountAuthenticatorIntent = reAuthenticateFuture.getResult().getParcelable(AccountManager.KEY_INTENT); + accountAuthenticatorIntent.putExtras(logoutUserIntent); + context.startActivity(logoutUserIntent); + + //logoff opensrp session + opensrpContent.userService().logoutSession(); + } + + @NonNull + private Intent getLogoutUserIntent() { + Intent intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.setPackage(context.getPackageName()); + //retrieve the main/launcher activity defined in the manifest and open it List activities = context.getPackageManager().queryIntentActivities(intent, 0); if (activities.size() == 1) { @@ -58,10 +81,9 @@ public void logoutUser() { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.putExtra(ACCOUNT_DISABLED, context.getString(R.string.account_disabled_logged_off)); - context.startActivity(intent); } - //logoff opensrp session - opensrpContent.userService().logoutSession(); + + return intent; } public boolean isAppVersionAllowed() throws PackageManager.NameNotFoundException { diff --git a/opensrp-app/src/test/java/org/smartregister/account/AccountHelperTest.java b/opensrp-app/src/test/java/org/smartregister/account/AccountHelperTest.java index 0a6417d15..9346512c7 100644 --- a/opensrp-app/src/test/java/org/smartregister/account/AccountHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/account/AccountHelperTest.java @@ -2,8 +2,10 @@ import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; +import android.os.Bundle; import org.junit.Assert; import org.junit.Before; @@ -90,4 +92,16 @@ public void testGetCachedOAuthToken() { Assert.assertEquals(TEST_TOKEN_VALUE, cachedAuthToken); } + @Test + public void testReAuthenticateUserAfterSessionExpired() { + + Account account = new Account(CORE_ACCOUNT_NAME, CORE_ACCOUNT_TYPE); + Mockito.doReturn(Mockito.mock(AccountManagerFuture.class)).when(accountManager).updateCredentials(account, AUTH_TOKEN_TYPE, null, null, null, null); + + AccountManagerFuture reAuthenticationFuture = AccountHelper.reAuthenticateUserAfterSessionExpired(CORE_ACCOUNT_NAME, CORE_ACCOUNT_TYPE, AUTH_TOKEN_TYPE); + Assert.assertNotNull(reAuthenticationFuture); + + Mockito.verify(accountManager).updateCredentials(account, AUTH_TOKEN_TYPE, null, null, null, null); + } + } diff --git a/opensrp-app/src/test/java/org/smartregister/sync/helper/SyncSettingsServiceHelperTest.java b/opensrp-app/src/test/java/org/smartregister/sync/helper/SyncSettingsServiceHelperTest.java index 26d513133..97b3d3959 100644 --- a/opensrp-app/src/test/java/org/smartregister/sync/helper/SyncSettingsServiceHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/sync/helper/SyncSettingsServiceHelperTest.java @@ -83,7 +83,7 @@ public void testProcessIntent() throws JSONException { params.add("locationId=location-uuid"); Mockito.doReturn(params).when(syncConfiguration).getExtraSettingsParameters(); - Mockito.doReturn(new Response<>(ResponseStatus.success, settingsResponse)).when(syncSettingsServiceHelper).getResponse(ArgumentMatchers.anyString()); + Mockito.doReturn(new Response<>(ResponseStatus.success, settingsResponse)).when(syncSettingsServiceHelper).getResponse(ArgumentMatchers.anyString(), ArgumentMatchers.anyString()); int size = syncSettingsServiceHelper.processIntent(); Assert.assertEquals(3, size); } From dd367d9594ae4a46530bcabb642786e97b7ce5e1 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Mon, 15 Jun 2020 10:18:10 +0300 Subject: [PATCH 37/70] Implement password hashing - Implement password salting and hashing for offline authentication --- .../smartregister/account/AccountHelper.java | 4 +- .../login/task/RemoteLoginTask.java | 8 + .../smartregister/security/PasswordHash.java | 23 +++ .../security/SecurityHelper.java | 69 +++++++- .../smartregister/service/UserService.java | 31 +++- .../security/SecurityHelperTest.java | 165 ++++++++++++++++++ 6 files changed, 289 insertions(+), 11 deletions(-) create mode 100644 opensrp-app/src/main/java/org/smartregister/security/PasswordHash.java create mode 100644 opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java index 91d757993..9ebf9cb37 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java @@ -44,10 +44,10 @@ public static final class INTENT_KEY { public final static String ACCOUNT_TYPE = "ACCOUNT_TYPE"; public final static String AUTH_TYPE = "AUTH_TYPE"; public final static String ACCOUNT_NAME = "ACCOUNT_NAME"; - public static final String ERROR_MESSAGE = "ERROR_MESSAGE"; - public final static String ACCOUNT_PASSWORD = "ACCOUNT_PASSWORD"; public final static String IS_NEW_ACCOUNT = "IS_NEW_ACCOUNT"; public final static String ACCOUNT_GROUP_ID = "ACCOUNT_GROUP_ID"; + public final static String ACCOUNT_PASSWORD = "ACCOUNT_PASSWORD"; + public final static String ACCOUNT_PASSWORD_SALT = "ACCOUNT_PASSWORD_SALT"; } public static final class TOKEN_TYPE { diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index c86268f6a..f08eff894 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -5,6 +5,7 @@ import android.accounts.AccountsException; import android.content.SharedPreferences; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import org.json.JSONArray; @@ -104,6 +105,13 @@ protected LoginResponse doInBackground(Void... params) { mAccountManager.setAuthToken(account, mLoginView.getAuthTokenType(), response.getAccessToken()); mAccountManager.setPassword(account, response.getRefreshToken()); mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID)); + mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_NAME, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_NAME)); + mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD)); + mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + mAccountManager.notifyAccountAuthenticated(account); + } if (getOpenSRPContext().userService().getGroupId(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { diff --git a/opensrp-app/src/main/java/org/smartregister/security/PasswordHash.java b/opensrp-app/src/main/java/org/smartregister/security/PasswordHash.java new file mode 100644 index 000000000..5aa684126 --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/security/PasswordHash.java @@ -0,0 +1,23 @@ +package org.smartregister.security; + +/** + * Created by ndegwamartin on 13/06/2020. + */ +public class PasswordHash { + + private byte[] salt; + private byte[] password; + + PasswordHash(byte[] salt, byte[] password) { + this.salt = salt; + this.password = password; + } + + public byte[] getSalt() { + return salt; + } + + public byte[] getPassword() { + return password; + } +} diff --git a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java index 4927f0553..8ba95e78b 100644 --- a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java @@ -1,22 +1,33 @@ package org.smartregister.security; +import android.os.Build; import android.text.Editable; +import android.util.Base64; import org.apache.commons.codec.CharEncoding; +import org.apache.commons.lang3.StringUtils; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; import java.util.Arrays; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + /** * Created by ndegwamartin on 04/06/2020. */ public class SecurityHelper { - private static Charset charset = Charset.forName(CharEncoding.UTF_8); + private static final Charset CHARSET = Charset.forName(CharEncoding.UTF_8); + public static final int ITERATION_COUNT = 200048; /** * This method ensures that sensitive info can be collected for the edit text in a safer way @@ -62,7 +73,7 @@ public static void clearArray(char[] array) { public static byte[] toBytes(StringBuffer stringBuffer) throws CharacterCodingException { - CharsetEncoder encoder = charset.newEncoder(); + CharsetEncoder encoder = CHARSET.newEncoder(); CharBuffer buffer = CharBuffer.wrap(stringBuffer); @@ -83,15 +94,15 @@ private static void clearStringBuffer(StringBuffer stringBuffer) { } /** - * This method converts characters in the string buffer to byte array without creating a String object + * This method converts characters in the char array buffer to a byte array * * @param chars array - * @return an array of bytes , a conversion from the chars array + * @return an array of bytes, a conversion from the chars array */ public static byte[] toBytes(char[] chars) { CharBuffer charBuffer = CharBuffer.wrap(chars); - ByteBuffer byteBuffer = charset.encode(charBuffer); + ByteBuffer byteBuffer = CHARSET.encode(charBuffer); byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()); @@ -101,6 +112,12 @@ public static byte[] toBytes(char[] chars) { } + /** + * This method converts characters in the byte array buffer to a char array + * + * @param bytes array + * @return an array of chars, a conversion from the bytes array + */ public static char[] toChars(byte[] bytes) { char[] convertedChar = new char[bytes.length]; @@ -109,6 +126,48 @@ public static char[] toChars(byte[] bytes) { } return convertedChar; + } + + /** + * @param password password to hash + * @return Password Hash object containing the bytes of the salt and the hashed password + * @throws NoSuchAlgorithmException when the Android API level doesn't support the specified Algorithm + * @throws InvalidKeySpecException when the Key used for generation has an invalid configuration + */ + public static PasswordHash getPasswordHash(char[] password) throws NoSuchAlgorithmException, InvalidKeySpecException { + + byte[] salt = new byte[128]; + + SecureRandom secureRandom = new SecureRandom(); + secureRandom.nextBytes(salt); + + return new PasswordHash(salt, hashPassword(password, salt)); + } + + /** + * @param password password to hash + * @return byte array of the bytes of the salt and the hashed password + */ + public static byte[] hashPassword(char[] password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { + + int keyLength = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? 256 : 160; + + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? "PBKDF2withHmacSHA256" : "PBKDF2WithHmacSHA1"); + KeySpec pbKeySpec = new PBEKeySpec(password, salt, ITERATION_COUNT, keyLength); + + return secretKeyFactory.generateSecret(pbKeySpec).getEncoded(); + } + + /** + * @param base64EncodedValue The base64 Encoded value + * @return decoded array of bytes + */ + public static byte[] nullSafeBase64Decode(String base64EncodedValue) { + if (!StringUtils.isBlank(base64EncodedValue)) { + return Base64.decode(base64EncodedValue, Base64.DEFAULT); + } else { + return null; + } } } diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index c49400f2b..2dfa00c50 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -24,6 +24,7 @@ import org.smartregister.domain.jsonmapping.util.TeamMember; import org.smartregister.repository.AllSettings; import org.smartregister.repository.AllSharedPreferences; +import org.smartregister.security.PasswordHash; import org.smartregister.security.SecurityHelper; import org.smartregister.sync.SaveANMLocationTask; import org.smartregister.sync.SaveANMTeamTask; @@ -45,6 +46,7 @@ import java.security.interfaces.RSAPublicKey; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.List; @@ -216,16 +218,33 @@ public boolean isUserInValidGroup(final String userName, final char[] password) // Check if everything OK for local login if (keyStore != null && userName != null && password != null && !allSharedPreferences.fetchForceRemoteLogin(userName)) { String username = userName.equalsIgnoreCase(allSharedPreferences.fetchRegisteredANM()) ? allSharedPreferences.fetchRegisteredANM() : userName; + byte[] storedHash = null; + byte[] passwordHash = null; + byte[] passwordSalt = null; try { KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(username); if (privateKeyEntry != null) { - char[] groupId = getGroupId(username, privateKeyEntry); - if (groupId != null) { - return isValidGroupId(groupId); + + // Compare stored password hash with provided password hash + storedHash = SecurityHelper.nullSafeBase64Decode(AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); + + passwordSalt = SecurityHelper.nullSafeBase64Decode(AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); + passwordHash = SecurityHelper.hashPassword(password, passwordSalt); + + if (storedHash != null && Arrays.equals(storedHash, passwordHash)) { + char[] groupId = getGroupId(username, privateKeyEntry); + if (groupId != null) { + return isValidGroupId(groupId); + } } } } catch (Exception e) { Timber.e(e); + } finally { + SecurityHelper.clearArray(password); + SecurityHelper.clearArray(passwordHash); + SecurityHelper.clearArray(passwordSalt); + SecurityHelper.clearArray(storedHash); } } @@ -563,6 +582,7 @@ public Bundle saveUserGroup(String userName, char[] password, LoginResponseData return null; } + PasswordHash passwordHash = SecurityHelper.getPasswordHash(password); SyncConfiguration syncConfiguration = CoreLibrary.getInstance().getSyncConfiguration(); if (syncConfiguration.getEncryptionParam() != null) { SyncFilter syncFilter = syncConfiguration.getEncryptionParam(); @@ -571,7 +591,7 @@ public Bundle saveUserGroup(String userName, char[] password, LoginResponseData } else if (SyncFilter.LOCATION.equals(syncFilter) || SyncFilter.LOCATION_ID.equals(syncFilter)) { groupId = SecurityHelper.toBytes(getUserLocationId(userInfo).toCharArray()); } else if (SyncFilter.PROVIDER.equals(syncFilter)) { - groupId = SecurityHelper.toBytes(new StringBuffer(username).append('-').append(password)); + groupId = SecurityHelper.toBytes(new StringBuffer(username).append('-').append(passwordHash.getPassword())); } } @@ -585,6 +605,9 @@ public Bundle saveUserGroup(String userName, char[] password, LoginResponseData String encryptedGroupId = encryptString(privateKeyEntry, groupId); bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, encryptedGroupId); + bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD, Base64.encodeToString(passwordHash.getPassword(), Base64.DEFAULT)); + bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, Base64.encodeToString(passwordHash.getSalt(), Base64.DEFAULT)); + // Finally, save the pioneer user if (allSharedPreferences.fetchPioneerUser() == null) { allSharedPreferences.savePioneerUser(username); diff --git a/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java b/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java new file mode 100644 index 000000000..afc6b6998 --- /dev/null +++ b/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java @@ -0,0 +1,165 @@ +package org.smartregister.security; + +import android.text.Editable; +import android.util.Base64; + +import org.apache.commons.codec.CharEncoding; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.CharacterCodingException; +import java.util.Arrays; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + + +/** + * Created by ndegwamartin on 15/06/2020. + */ + +@RunWith(PowerMockRunner.class) +@PrepareForTest({Base64.class, SecretKeyFactory.class}) +public class SecurityHelperTest { + + @Mock + private Editable editable; + + @Mock + private SecretKey secretKey; + + @Mock + private PBEKeySpec keySpec; + + private char[] TEST_PASSWORD; + + private static final String TEST_DATA = "Some Random Test Data"; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + TEST_PASSWORD = "TEST_PASSWORD".toCharArray(); + } + + + @Test + public void testReadValueClearsEditableAfterReadingValue() { + + Mockito.doReturn(2).when(editable).length(); + + SecurityHelper.readValue(editable); + + ArgumentCaptor lengthCaptor = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor charsCaptor = ArgumentCaptor.forClass(char[].class); + ArgumentCaptor firstArgCaptor = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor lastArgCaptor = ArgumentCaptor.forClass(Integer.class); + + Mockito.verify(editable).getChars(firstArgCaptor.capture(), lengthCaptor.capture(), charsCaptor.capture(), lastArgCaptor.capture()); + Mockito.verify(editable).clear(); + + Assert.assertEquals(2, lengthCaptor.getValue().intValue()); + Assert.assertEquals(0, firstArgCaptor.getValue().intValue()); + Assert.assertEquals(0, lastArgCaptor.getValue().intValue()); + + } + + @Test + public void clearArray() { + byte[] sensitiveDataArray = SecurityHelper.toBytes(TEST_PASSWORD); + SecurityHelper.clearArray(sensitiveDataArray); + + Assert.assertNotNull(sensitiveDataArray); + + for (byte c : sensitiveDataArray) { + Assert.assertEquals((byte) 0, c); + + } + } + + @Test + public void testClearArrayOverwritesCharArrayValuesWithAsterisk() { + char[] sensitiveDataArray = TEST_PASSWORD; + SecurityHelper.clearArray(sensitiveDataArray); + + Assert.assertNotNull(sensitiveDataArray); + + for (char c : sensitiveDataArray) { + Assert.assertEquals('*', c); + + } + } + + @Test + public void testToBytes() throws CharacterCodingException { + + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append(TEST_PASSWORD); + + byte[] testPasswordBytes = SecurityHelper.toBytes(stringBuffer); + Assert.assertNotNull(testPasswordBytes); + Assert.assertEquals(TEST_PASSWORD.length + 1, testPasswordBytes.length); + } + + @Test + public void testToCharsConvertsByteArrayToCorrectCharArray() { + + byte[] testPasswordBytes = SecurityHelper.toBytes(TEST_PASSWORD); + Assert.assertNotNull(testPasswordBytes); + + char[] testPasswordChars = SecurityHelper.toChars(testPasswordBytes); + Assert.assertNotNull(testPasswordChars); + Assert.assertTrue(Arrays.equals(TEST_PASSWORD, testPasswordChars)); + } + + @Test + public void nullSafeBase64DecodeDecodesValidBase64EncodedCorrectly() throws UnsupportedEncodingException { + + String base64EncodedString = "U29tZSBSYW5kb20gVGVzdCBEYXRh"; + + PowerMockito.mockStatic(Base64.class); + PowerMockito.when(Base64.decode(base64EncodedString, Base64.DEFAULT)).thenReturn(TEST_DATA.getBytes(CharEncoding.UTF_8)); + + byte[] decoded = SecurityHelper.nullSafeBase64Decode(base64EncodedString); + Assert.assertNotNull(decoded); + Assert.assertTrue(Arrays.equals(SecurityHelper.toBytes(TEST_DATA.toCharArray()), decoded)); + + } + + @Test + public void nullSafeBase64DecodeDoesNotThrowExceptionIfParameterIsNull() { + PowerMockito.mockStatic(Base64.class); + PowerMockito.when(Base64.decode(ArgumentMatchers.anyString(), ArgumentMatchers.eq(Base64.DEFAULT))).thenReturn(new byte[]{0, 1}); + + byte[] decoded = SecurityHelper.nullSafeBase64Decode(null); + Assert.assertNull(decoded); + + } + + @Test + public void testGetPsswordHashReturnsHashedPasswordObject() throws Exception { + + PowerMockito.mockStatic(SecretKeyFactory.class); + SecretKeyFactory keyFactory = PowerMockito.mock(SecretKeyFactory.class); + PowerMockito.when(SecretKeyFactory.getInstance(ArgumentMatchers.anyString())).thenReturn(keyFactory); + PowerMockito.when(keyFactory.generateSecret(ArgumentMatchers.any(PBEKeySpec.class))).thenReturn(secretKey); + Mockito.doReturn(SecurityHelper.toBytes(TEST_PASSWORD)).when(secretKey).getEncoded(); + + PasswordHash passwordHash = SecurityHelper.getPasswordHash(TEST_PASSWORD); + + Assert.assertNotNull(passwordHash); + Assert.assertNotNull(passwordHash.getPassword()); + Assert.assertNotNull(passwordHash.getSalt()); + } +} \ No newline at end of file From e3ca2b498b15a2d37fee12e0a3d49c96874589a8 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Mon, 15 Jun 2020 14:25:45 +0300 Subject: [PATCH 38/70] Migrate Sample app to new core library --- .../sample/application/SampleSyncConfiguration.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sample/src/main/java/org/smartregister/sample/application/SampleSyncConfiguration.java b/sample/src/main/java/org/smartregister/sample/application/SampleSyncConfiguration.java index 205ab4b80..75333cca1 100644 --- a/sample/src/main/java/org/smartregister/sample/application/SampleSyncConfiguration.java +++ b/sample/src/main/java/org/smartregister/sample/application/SampleSyncConfiguration.java @@ -5,6 +5,7 @@ import org.smartregister.SyncFilter; import org.smartregister.repository.AllSharedPreferences; import org.smartregister.sample.BuildConfig; +import org.smartregister.view.activity.BaseLoginActivity; import java.util.List; @@ -72,4 +73,9 @@ public String getOauthClientId() { public String getOauthClientSecret() { return BuildConfig.OAUTH_CLIENT_SECRET; } + + @Override + public Class getAuthenticationActivity() { + return null; + } } From d03f352b3cf4c8a35ebeb85a972426b1eba46fe8 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Mon, 15 Jun 2020 14:47:43 +0300 Subject: [PATCH 39/70] Refactor local login - Refactor local login to run in backgroud - Fixes UI freeze during offline Authentication --- .../login/interactor/BaseLoginInteractor.java | 23 ++++++--- .../login/task/LocalLoginTask.java | 49 +++++++++++++++++++ .../login/task/RemoteLoginTask.java | 4 +- .../smartregister/service/UserService.java | 4 +- .../view/activity/BaseLoginActivity.java | 14 +++--- 5 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 opensrp-app/src/main/java/org/smartregister/login/task/LocalLoginTask.java diff --git a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java index a4c06ba20..cc4c8ac7b 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java +++ b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java @@ -20,6 +20,7 @@ import org.smartregister.job.PullUniqueIdsServiceJob; import org.smartregister.job.SyncSettingsServiceJob; import org.smartregister.listener.OnCompleteClearDataCallback; +import org.smartregister.login.task.LocalLoginTask; import org.smartregister.login.task.RemoteLoginTask; import org.smartregister.multitenant.ResetAppHelper; import org.smartregister.repository.AllSharedPreferences; @@ -83,18 +84,24 @@ public void loginWithLocalFlag(WeakReference view, boole private void localLogin(WeakReference view, String userName, char[] password) { getLoginView().enableLoginButton(true); - boolean isAuthenticated = getUserService().isUserInValidGroup(userName, password); - if (!isAuthenticated) { - getLoginView().showErrorDialog(getApplicationContext().getResources().getString(R.string.unauthorized)); + new LocalLoginTask(view.get(), userName, password, isAuthenticated -> { - } else if (isAuthenticated && (!AllConstants.TIME_CHECK || TimeStatus.OK.equals(getUserService().validateStoredServerTimeZone()))) { + if (!isAuthenticated) { - navigateToHomePage(userName, password); + getLoginView().showErrorDialog(getApplicationContext().getResources().getString(R.string.unauthorized)); + + } else if (isAuthenticated && (!AllConstants.TIME_CHECK || TimeStatus.OK.equals(getUserService().validateStoredServerTimeZone()))) { + + navigateToHomePage(userName, password); + + } else { + loginWithLocalFlag(view, false, userName, password); + } + + + }).execute(); - } else { - loginWithLocalFlag(view, false, userName, password); - } } private void navigateToHomePage(String userName, char[] password) { diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/LocalLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/LocalLoginTask.java new file mode 100644 index 000000000..316db3c2b --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/login/task/LocalLoginTask.java @@ -0,0 +1,49 @@ +package org.smartregister.login.task; + +import android.os.AsyncTask; + +import org.smartregister.CoreLibrary; +import org.smartregister.event.Listener; +import org.smartregister.service.UserService; +import org.smartregister.view.contract.BaseLoginContract; + +/** + * Created by ndegwamartin on 13/06/2020. + */ +public class LocalLoginTask extends AsyncTask { + + private BaseLoginContract.View mLoginView; + private final String mUsername; + private final char[] mPassword; + private final Listener mAfterLoginCheck; + + public LocalLoginTask(BaseLoginContract.View loginView, String username, char[] password, Listener afterLoginCheck) { + mLoginView = loginView; + mUsername = username; + mPassword = password; + mAfterLoginCheck = afterLoginCheck; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + mLoginView.showProgress(true); + } + + @Override + protected Boolean doInBackground(Void... voids) { + return getUserService().isUserInValidGroup(mUsername, mPassword); + } + + @Override + protected void onPostExecute(final Boolean loginResponse) { + super.onPostExecute(loginResponse); + + mLoginView.showProgress(false); + mAfterLoginCheck.onEvent(loginResponse); + } + + private UserService getUserService() { + return CoreLibrary.getInstance().context().userService(); + } +} diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index f08eff894..2050ca72d 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -65,9 +65,9 @@ protected LoginResponse doInBackground(Void... params) { AccountConfiguration accountConfiguration = CoreLibrary.getInstance().context().getHttpAgent().fetchOAuthConfiguration(); - boolean isKeyclockConfigured = accountConfiguration != null; + boolean isKeycloakConfigured = accountConfiguration != null; - if (!isKeyclockConfigured) { + if (!isKeycloakConfigured) { accountConfiguration = new AccountConfiguration(); accountConfiguration.setGrantTypesSupported(Arrays.asList(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD)); accountConfiguration.setTokenEndpoint(CoreLibrary.getInstance().context().configuration().dristhiBaseURL() + AccountHelper.OAUTH.TOKEN_ENDPOINT); diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 2dfa00c50..2dfcf4edb 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -298,7 +298,7 @@ public boolean isUserInPioneerGroup(String userName) { char[] userGroupId = getGroupId(userName); char[] pioneerGroupId = getGroupId(pioneerUser); - if (userGroupId != null && userGroupId.equals(pioneerGroupId)) { + if (userGroupId != null && Arrays.equals(pioneerGroupId, userGroupId)) { return isValidGroupId(userGroupId); } } @@ -570,12 +570,12 @@ public Bundle saveUserGroup(String userName, char[] password, LoginResponseData String username = userInfo.user != null && StringUtils.isNotBlank(userInfo.user.getUsername()) ? userInfo.user.getUsername() : userName; bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_NAME, username); - if (keyStore != null && username != null) { byte[] groupId = null; try { + KeyStore.PrivateKeyEntry privateKeyEntry = createUserKeyPair(username); if (password == null) { diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java index b21445e79..eddf5a256 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java @@ -207,9 +207,7 @@ public void enableLoginButton(boolean isClickable) { @Override public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { if (actionId == R.integer.login || actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) { - String username = userNameEditText.getText().toString(); - char[] password = SecurityHelper.readValue(passwordEditText.getText()); - mLoginPresenter.attemptLogin(username, password); + attemptLogin(); return true; } return false; @@ -218,12 +216,16 @@ public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent @Override public void onClick(View v) { if (v.getId() == R.id.login_login_btn) { - String username = userNameEditText.getText().toString(); - char[] password = SecurityHelper.readValue(passwordEditText.getText()); - mLoginPresenter.attemptLogin(username, password); + attemptLogin(); } } + protected void attemptLogin() { + String username = userNameEditText.getText().toString().trim(); + char[] password = SecurityHelper.readValue(passwordEditText.getText()); + mLoginPresenter.attemptLogin(username, password); + } + @Override public void setUsernameError(int resourceId) { userNameEditText.setError(getString(resourceId)); From d5aee185c2b11a5d63b608b3617d40d2c2f16e61 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Mon, 15 Jun 2020 14:12:27 +0300 Subject: [PATCH 40/70] Support for local Multi-tenancy - Refactor offline multi tenancy implementation for same Team Members - Adds support for No need to remotely authenticate if all team members had logged in device previously --- .../src/test/java/org/smartregister/service/HTTPAgentTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index 87c46c40b..0d17bf656 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -175,6 +175,8 @@ public void setUp() { Mockito.doReturn(syncConfiguration).when(coreLibrary).getSyncConfiguration(); Mockito.doReturn(1).when(syncConfiguration).getMaxAuthenticationRetries(); + Mockito.doReturn(TEST_USERNAME).when(allSharedPreferences).fetchRegisteredANM(); + PowerMockito.mockStatic(AccountHelper.class); PowerMockito.when(AccountHelper.getOauthAccountByNameAndType(TEST_USERNAME, accountAuthenticatorXml.getAccountType())).thenReturn(account); PowerMockito.when(AccountHelper.getOAuthToken(TEST_USERNAME, accountAuthenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER)).thenReturn(SAMPLE_TEST_TOKEN); From 2172f350c579c3d4ab033e215f295b90ab41fcf5 Mon Sep 17 00:00:00 2001 From: qiarie Date: Tue, 30 Jun 2020 18:50:12 +0300 Subject: [PATCH 41/70] Support for specifying key-value metadata map when launching form - Add support for key-value pairs for start JSON form - overload startFormActivity - Refactor LocationHelper to use List interface instead of ArrayList - Expose AllowedLevels and DefaultLocationId fields on LocationHelper Co-authored-by: Martin Ndegwa --- .../adapter/ServiceLocationsAdapter.java | 7 ++- .../location/helper/LocationHelper.java | 56 +++++++++++-------- .../view/LocationPickerView.java | 5 +- .../view/activity/BaseRegisterActivity.java | 4 +- .../view/activity/SecuredActivity.java | 8 +++ .../view/contract/BaseRegisterContract.java | 1 + 6 files changed, 50 insertions(+), 31 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/adapter/ServiceLocationsAdapter.java b/opensrp-app/src/main/java/org/smartregister/adapter/ServiceLocationsAdapter.java index e12bffc89..08ea4b06b 100644 --- a/opensrp-app/src/main/java/org/smartregister/adapter/ServiceLocationsAdapter.java +++ b/opensrp-app/src/main/java/org/smartregister/adapter/ServiceLocationsAdapter.java @@ -13,18 +13,19 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /** * @author Jason Rogena - jrogena@ona.io * @since 03/03/2017 */ public class ServiceLocationsAdapter extends BaseAdapter { - private final ArrayList locationNames; + private final List locationNames; private final HashMap views; private final Context context; private String selectedLocation; - public ServiceLocationsAdapter(Context context, ArrayList locationNames) { + public ServiceLocationsAdapter(Context context, List locationNames) { this.context = context; this.locationNames = locationNames == null ? new ArrayList<>() : locationNames; this.views = new HashMap<>(); @@ -91,7 +92,7 @@ public String getLocationAt(int position) { return locationNames.get(position); } - public ArrayList getLocationNames() { + public List getLocationNames() { return locationNames; } } diff --git a/opensrp-app/src/main/java/org/smartregister/location/helper/LocationHelper.java b/opensrp-app/src/main/java/org/smartregister/location/helper/LocationHelper.java index d9fec7290..9222eeea1 100644 --- a/opensrp-app/src/main/java/org/smartregister/location/helper/LocationHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/location/helper/LocationHelper.java @@ -40,18 +40,18 @@ public class LocationHelper { private String childLocationId; private String parentLocationId; private List locationIds; - private ArrayList locationNames; + private List locationNames; private List locationNameHierarchy; - private HashMap> childAndParentLocationIds; + private Map> childAndParentLocationIds; private String defaultLocation; - private ArrayList ALLOWED_LEVELS; + private List ALLOWED_LEVELS; private String DEFAULT_LOCATION_LEVEL; private List allCampaigns = new ArrayList<>(); private List allOperationalArea = new ArrayList<>(); private AllSharedPreferences allSharedPreferences = CoreLibrary.getInstance().context().allSharedPreferences(); - private LocationHelper(ArrayList allowedLevels, String defaultLocationLevel) { + private LocationHelper(List allowedLevels, String defaultLocationLevel) { childAndParentLocationIds = new HashMap<>(); setParentAndChildLocationIds(getDefaultLocation()); @@ -59,7 +59,7 @@ private LocationHelper(ArrayList allowedLevels, String defaultLocationLe this.DEFAULT_LOCATION_LEVEL = defaultLocationLevel; } - public static void init(ArrayList allowedLevels, String defaultLocationLevel) { + public static void init(List allowedLevels, String defaultLocationLevel) { if (instance == null && StringUtils.isNotEmpty(defaultLocationLevel) && allowedLevels != null && allowedLevels.contains(defaultLocationLevel)) { instance = new LocationHelper(allowedLevels, defaultLocationLevel); } @@ -80,15 +80,15 @@ public String locationIdsFromHierarchy() { return null; } - public ArrayList locationNamesFromHierarchy(String defaultLocation) { + public List locationNamesFromHierarchy(String defaultLocation) { if (Utils.isEmptyCollection(locationNames)) { locationNames = locationsFromHierarchy(false, defaultLocation); } return locationNames; } - public ArrayList locationsFromHierarchy(boolean fetchLocationIds, String defaultLocation) { - ArrayList locations = new ArrayList<>(); + public List locationsFromHierarchy(boolean fetchLocationIds, String defaultLocation) { + List locations = new ArrayList<>(); try { LinkedHashMap> map = map(); if (!Utils.isEmptyMap(map)) { @@ -195,7 +195,7 @@ public List getOpenMrsLocationHierarchy(String locationId, boolean onlyA LinkedHashMap> map = map(); if (!Utils.isEmptyMap(map)) { for (Map.Entry> entry : map.entrySet()) { - List curResult = getOpenMrsLocationHierarchy(locationId, entry.getValue(), new ArrayList(), onlyAllowedLevels); + List curResult = getOpenMrsLocationHierarchy(locationId, entry.getValue(), new ArrayList<>(), onlyAllowedLevels); if (!Utils.isEmptyCollection(curResult)) { response = curResult; break; @@ -211,7 +211,7 @@ public List getOpenMrsLocationHierarchy(String locationId, boolean onlyA } - public List generateDefaultLocationHierarchy(ArrayList allowedLevels) { + public List generateDefaultLocationHierarchy(List allowedLevels) { if (Utils.isEmptyCollection(allowedLevels)) { return new ArrayList<>(); } @@ -222,7 +222,7 @@ public List generateDefaultLocationHierarchy(ArrayList allowedLe LinkedHashMap> map = map(); if (!Utils.isEmptyMap(map)) { for (Map.Entry> entry : map.entrySet()) { - List curResult = getDefaultLocationHierarchy(defaultLocationUuid, entry.getValue(), new ArrayList(), allowedLevels); + List curResult = getDefaultLocationHierarchy(defaultLocationUuid, entry.getValue(), new ArrayList<>(), allowedLevels); if (!Utils.isEmptyCollection(curResult)) { return curResult; } @@ -234,12 +234,12 @@ public List generateDefaultLocationHierarchy(ArrayList allowedLe return null; } - public List generateLocationHierarchyTree(boolean withOtherOption, ArrayList allowedLevels) { + public List generateLocationHierarchyTree(boolean withOtherOption, List allowedLevels) { LinkedHashMap> map = map(); - return generateLocationHierarchyTree(withOtherOption,allowedLevels,map); + return generateLocationHierarchyTree(withOtherOption, allowedLevels, map); } - public List generateLocationHierarchyTree(boolean withOtherOption, ArrayList allowedLevels, Map> map) { + public List generateLocationHierarchyTree(boolean withOtherOption, List allowedLevels, Map> map) { if (Utils.isEmptyCollection(allowedLevels)) { return new ArrayList<>(); } @@ -296,9 +296,9 @@ public String getOpenMrsReadableName(String name) { } // Private methods - private ArrayList extractLocations(TreeNode rawLocationData, boolean fetchLocationIds, String defaultLocation) { + private List extractLocations(TreeNode rawLocationData, boolean fetchLocationIds, String defaultLocation) { - ArrayList locationList = new ArrayList<>(); + List locationList = new ArrayList<>(); try { if (rawLocationData == null) { return null; @@ -335,7 +335,7 @@ private ArrayList extractLocations(TreeNode rawLocatio LinkedHashMap> childMap = childMap(rawLocationData); if (!Utils.isEmptyMap(childMap)) { for (Map.Entry> childEntry : childMap.entrySet()) { - ArrayList childLocations = extractLocations(childEntry.getValue(), fetchLocationIds, defaultLocation); + List childLocations = extractLocations(childEntry.getValue(), fetchLocationIds, defaultLocation); if (!Utils.isEmptyCollection(childLocations)) { locationList.addAll(childLocations); } @@ -413,9 +413,9 @@ private String getOpenMrsLocationName(String locationId, TreeNode getDefaultLocationHierarchy(String defaultLocationUuid, TreeNode openMrsLocationData, List parents, ArrayList allowedLevels) { + Location> openMrsLocationData, List parents, List allowedLevels) { try { - List heirachy = new ArrayList<>(parents); + List hierarchy = new ArrayList<>(parents); if (openMrsLocationData == null) { return null; } @@ -429,19 +429,19 @@ public List getDefaultLocationHierarchy(String defaultLocationUuid, Tree if (!Utils.isEmptyCollection(levels)) { for (String level : levels) { if (allowedLevels.contains(level)) { - heirachy.add(node.getName()); + hierarchy.add(node.getName()); } } } if (defaultLocationUuid.equals(node.getLocationId())) { - return heirachy; + return hierarchy; } LinkedHashMap> childMap = childMap(openMrsLocationData); if (!Utils.isEmptyMap(childMap)) { for (Map.Entry> childEntry : childMap.entrySet()) { - List curResult = getDefaultLocationHierarchy(defaultLocationUuid, childEntry.getValue(), heirachy, allowedLevels); + List curResult = getDefaultLocationHierarchy(defaultLocationUuid, childEntry.getValue(), hierarchy, allowedLevels); if (!Utils.isEmptyCollection(curResult)) { return curResult; } @@ -454,7 +454,7 @@ public List getDefaultLocationHierarchy(String defaultLocationUuid, Tree } private List getFormJsonData(TreeNode openMrsLocationData, - ArrayList allowedLevels) { + List allowedLevels) { List allLocationData = new ArrayList<>(); try { FormLocation formLocation = new FormLocation(); @@ -536,7 +536,7 @@ protected boolean isLocationTagsShownEnabled() { sortMap.put(formLocation.name, formLocation); } - ArrayList sortedKeys = new ArrayList<>(sortMap.keySet()); + List sortedKeys = new ArrayList<>(sortMap.keySet()); Collections.sort(sortedKeys); for (String curOptionName : sortedKeys) { @@ -681,4 +681,12 @@ public LinkedHashMap> map() { return null; } + public List getAllowedLevels() { + return ALLOWED_LEVELS; + } + + public String getDefaultLocationLevel() { + return DEFAULT_LOCATION_LEVEL; + } + } \ No newline at end of file diff --git a/opensrp-app/src/main/java/org/smartregister/view/LocationPickerView.java b/opensrp-app/src/main/java/org/smartregister/view/LocationPickerView.java index 90bde89c4..dd177afbe 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/LocationPickerView.java +++ b/opensrp-app/src/main/java/org/smartregister/view/LocationPickerView.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.List; /** * @author Jason Rogena - jrogena@ona.io @@ -94,8 +95,8 @@ public void setOnLocationChangeListener(final OnLocationChangeListener onLocatio this.onLocationChangeListener = onLocationChangeListener; } - private ArrayList getLocations(String defaultLocation) { - ArrayList locations = LocationHelper.getInstance().locationNamesFromHierarchy(defaultLocation); + private List getLocations(String defaultLocation) { + List locations = LocationHelper.getInstance().locationNamesFromHierarchy(defaultLocation); if (locations.contains(defaultLocation)) { locations.remove(defaultLocation); diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseRegisterActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseRegisterActivity.java index da8e505c7..41dc7be64 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseRegisterActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseRegisterActivity.java @@ -33,6 +33,7 @@ import org.smartregister.view.viewpager.OpenSRPViewPager; import java.util.List; +import java.util.Map; import timber.log.Timber; @@ -198,8 +199,7 @@ protected void onInitialization() {//Implement Abstract Method } @Override - public abstract void startFormActivity(String formName, String entityId, String metaData); - + public abstract void startFormActivity(String formName, String entityId, Map metaData); @Override public abstract void startFormActivity(JSONObject form); diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/SecuredActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/SecuredActivity.java index 9c56aeab4..2f1af677a 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/SecuredActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/SecuredActivity.java @@ -17,6 +17,7 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import org.apache.commons.collections.MapUtils; import org.smartregister.AllConstants; import org.smartregister.Context; import org.smartregister.CoreLibrary; @@ -32,6 +33,7 @@ import org.smartregister.view.customcontrols.ProcessingInProgressSnackbar; import java.util.Map; +import java.util.Optional; import timber.log.Timber; @@ -164,6 +166,12 @@ public void startFormActivity(String formName, String entityId, String metaData) launchForm(formName, entityId, metaData, FormActivity.class); } + public void startFormActivity(String formName, String entityId, Map metaData) { + String metaDataString = MapUtils.getString(metaData, FIELD_OVERRIDES_PARAM, ""); + + launchForm(formName, entityId, metaDataString, FormActivity.class); + } + public void startMicroFormActivity(String formName, String entityId, String metaData) { launchForm(formName, entityId, metaData, MicroFormActivity.class); } diff --git a/opensrp-app/src/main/java/org/smartregister/view/contract/BaseRegisterContract.java b/opensrp-app/src/main/java/org/smartregister/view/contract/BaseRegisterContract.java index 16a235981..ab717e495 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/contract/BaseRegisterContract.java +++ b/opensrp-app/src/main/java/org/smartregister/view/contract/BaseRegisterContract.java @@ -6,6 +6,7 @@ import org.smartregister.domain.FetchStatus; import java.util.List; +import java.util.Map; /** * Created by keyamn on 27/06/2018. From 8db5c2d92cc1beff5c1bff8f2e7fd50caff02d7e Mon Sep 17 00:00:00 2001 From: qiarie Date: Thu, 2 Jul 2020 16:44:20 +0300 Subject: [PATCH 42/70] Add unit tests - Add unit tests for startFormActivity with map metadata params - Refactor tests to use List interface instead of ArrayList class Co-authored-by: Martin Ndegwa --- .../adapter/ServiceLocationsAdapterTest.java | 3 +- .../location/helper/LocationHelperTest.java | 2 +- .../smartregister/service/HTTPAgentTest.java | 7 +++- .../viewholder/OnClickFormLauncherTest.java | 39 +++++++++++++++++-- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/opensrp-app/src/test/java/org/smartregister/adapter/ServiceLocationsAdapterTest.java b/opensrp-app/src/test/java/org/smartregister/adapter/ServiceLocationsAdapterTest.java index d1b3ffd2e..709e448d2 100644 --- a/opensrp-app/src/test/java/org/smartregister/adapter/ServiceLocationsAdapterTest.java +++ b/opensrp-app/src/test/java/org/smartregister/adapter/ServiceLocationsAdapterTest.java @@ -18,6 +18,7 @@ import org.smartregister.location.helper.LocationHelper; import java.util.ArrayList; +import java.util.List; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @@ -79,7 +80,7 @@ public void testGetLocationAt() { public void testGetLocationNames() { ServiceLocationsAdapter adapter = getAdapterWithFakeClients(); - ArrayList names = adapter.getLocationNames(); + List names = adapter.getLocationNames(); Assert.assertEquals(names.get(0), "test1"); } diff --git a/opensrp-app/src/test/java/org/smartregister/location/helper/LocationHelperTest.java b/opensrp-app/src/test/java/org/smartregister/location/helper/LocationHelperTest.java index f84a8e48a..5f6b1d131 100644 --- a/opensrp-app/src/test/java/org/smartregister/location/helper/LocationHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/location/helper/LocationHelperTest.java @@ -227,7 +227,7 @@ public void testLocationsFromHierarchyWhenAllowedLevelsContainsReveal() { ReflectionHelpers.setField(spiedLocationHelper, "allCampaigns", campaignIds); ReflectionHelpers.setField(spiedLocationHelper, "allOperationalArea", operationalArea); - ArrayList locations = spiedLocationHelper.locationsFromHierarchy(true, null); + List locations = spiedLocationHelper.locationsFromHierarchy(true, null); Mockito.verify(allSharedPreferences).savePreference(Mockito.eq(AllConstants.CAMPAIGNS), Mockito.eq("campaign-1,campaign-2,campaign-3")); Mockito.verify(allSharedPreferences).savePreference(Mockito.eq(AllConstants.OPERATIONAL_AREAS), Mockito.eq("operational-area-1,operational-area-2,operational-area-3")); diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index 0d17bf656..85849f16a 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -143,7 +143,7 @@ public class HTTPAgentTest { private static final String TEST_USER_INFO_ENDPOINT = "https://keycloak.my-server.com/auth/userinfo"; private static final String TEST_IMAGE_FILE_PATH = "file://usr/sdcard/dev0/data/org.smartregister.core/localimage.jpg"; protected static final String TEST_BASE_ENTITY_ID = "23ka2-3e23h2-n3g2i4-9q3b-yts4-20"; - protected static final String TEST_ANM_ID = "demo"; + protected static final String TEST_ACCOUNT_TYPE = "org.smartregister.my-health-app"; private final String SAMPLE_TEST_TOKEN = "sample-test-token"; private final String SAMPLE_REFRESH_TOKEN = "sample-refresh-token"; @@ -169,11 +169,14 @@ public void setUp() { PowerMockito.mockStatic(CoreLibrary.class); PowerMockito.when(CoreLibrary.getInstance()).thenReturn(coreLibrary); Mockito.doReturn(accountManager).when(coreLibrary).getAccountManager(); + Mockito.doReturn(TEST_ACCOUNT_TYPE).when(accountAuthenticatorXml).getAccountType(); + Mockito.doReturn(TEST_USERNAME).when(accountAuthenticatorXml).getAccountName(); Mockito.doReturn(accountAuthenticatorXml).when(coreLibrary).getAccountAuthenticatorXml(); Mockito.doReturn(accountManager).when(coreLibrary).getAccountManager(); Mockito.doReturn(syncConfiguration).when(coreLibrary).getSyncConfiguration(); Mockito.doReturn(1).when(syncConfiguration).getMaxAuthenticationRetries(); + Mockito.doReturn(TEST_USERNAME).when(allSharedPreferences).fetchRegisteredANM(); Mockito.doReturn(TEST_USERNAME).when(allSharedPreferences).fetchRegisteredANM(); @@ -1104,7 +1107,7 @@ public void testHttpImagePostConfiguresConnectionRequestCorrectly() throws Excep ProfileImage profileImage = new ProfileImage(); profileImage.setFilepath(TEST_IMAGE_FILE_PATH); - profileImage.setAnmId(TEST_ANM_ID); + profileImage.setAnmId(TEST_USERNAME); profileImage.setEntityID(TEST_BASE_ENTITY_ID); profileImage.setContenttype("png"); profileImage.setFilecategory("coverpic"); diff --git a/opensrp-app/src/test/java/org/smartregister/view/viewholder/OnClickFormLauncherTest.java b/opensrp-app/src/test/java/org/smartregister/view/viewholder/OnClickFormLauncherTest.java index d21f0b839..ff190011b 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/viewholder/OnClickFormLauncherTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/viewholder/OnClickFormLauncherTest.java @@ -11,6 +11,9 @@ import org.smartregister.BaseUnitTest; import org.smartregister.view.activity.SecuredActivity; +import java.util.HashMap; +import java.util.Map; + /** * Created by ndegwamartin on 2020-04-14. */ @@ -46,15 +49,45 @@ public void testOnClickInvokesStartFormActivityWithCorrectParams() { @Test public void testOnClickInvokesStartFormActivityWithCorrectParamsNoMetadata() { - - Mockito.doNothing().when(activity).startFormActivity(TEST_FORM_NAME, TEST_BASE_ENTITY_ID, null); + Mockito.doNothing().when(activity).startFormActivity(TEST_FORM_NAME, TEST_BASE_ENTITY_ID, ""); OnClickFormLauncher onClickFormLauncher = new OnClickFormLauncher(activity, TEST_FORM_NAME, TEST_BASE_ENTITY_ID); Assert.assertNotNull(onClickFormLauncher); onClickFormLauncher.onClick(view); - Mockito.verify(activity).startFormActivity(TEST_FORM_NAME, TEST_BASE_ENTITY_ID, null); + Mockito.verify(activity).startFormActivity(TEST_FORM_NAME, TEST_BASE_ENTITY_ID, (String) null); + + } + + @Test + public void testOnClickInvokesStartFormActivityWithCorrectMapMetadataParams() { + Map testMetadata = new HashMap<>(); + testMetadata.put("first_name", "john"); + testMetadata.put("last_name", "doe"); + + Mockito.doNothing().when(activity).startFormActivity(TEST_FORM_NAME, TEST_BASE_ENTITY_ID, testMetadata); + + OnClickFormLauncher onClickFormLauncher = new OnClickFormLauncher(activity, TEST_FORM_NAME, TEST_BASE_ENTITY_ID, testMetadata.toString()); + Assert.assertNotNull(onClickFormLauncher); + + onClickFormLauncher.onClick(view); + Mockito.verify(activity, Mockito.times(1)).startFormActivity(TEST_FORM_NAME, TEST_BASE_ENTITY_ID, testMetadata.toString()); } + + @Test + public void testOnClickInvokesStartFormActivityWithEmptyMapMetadataParams() { + Map testMetadata = new HashMap<>(); + + Mockito.doNothing().when(activity).startFormActivity(TEST_FORM_NAME, TEST_BASE_ENTITY_ID, testMetadata); + + OnClickFormLauncher onClickFormLauncher = new OnClickFormLauncher(activity, TEST_FORM_NAME, TEST_BASE_ENTITY_ID, testMetadata.toString()); + Assert.assertNotNull(onClickFormLauncher); + + onClickFormLauncher.onClick(view); + + Mockito.verify(activity, Mockito.times(1)).startFormActivity(TEST_FORM_NAME, TEST_BASE_ENTITY_ID, testMetadata.toString()); + } + } From 4b18d628a2b29f8599dbfd8ed08782b6cfa313ce Mon Sep 17 00:00:00 2001 From: qiarie Date: Thu, 2 Jul 2020 17:25:36 +0300 Subject: [PATCH 43/70] Fix Codacy issues --- .../src/main/java/org/smartregister/view/LocationPickerView.java | 1 - .../java/org/smartregister/view/activity/SecuredActivity.java | 1 - .../org/smartregister/view/contract/BaseRegisterContract.java | 1 - 3 files changed, 3 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/view/LocationPickerView.java b/opensrp-app/src/main/java/org/smartregister/view/LocationPickerView.java index dd177afbe..ad17588d8 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/LocationPickerView.java +++ b/opensrp-app/src/main/java/org/smartregister/view/LocationPickerView.java @@ -18,7 +18,6 @@ import org.smartregister.location.helper.LocationHelper; import org.smartregister.view.customcontrols.CustomFontTextView; -import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/SecuredActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/SecuredActivity.java index 2f1af677a..7ae42b168 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/SecuredActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/SecuredActivity.java @@ -33,7 +33,6 @@ import org.smartregister.view.customcontrols.ProcessingInProgressSnackbar; import java.util.Map; -import java.util.Optional; import timber.log.Timber; diff --git a/opensrp-app/src/main/java/org/smartregister/view/contract/BaseRegisterContract.java b/opensrp-app/src/main/java/org/smartregister/view/contract/BaseRegisterContract.java index ab717e495..16a235981 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/contract/BaseRegisterContract.java +++ b/opensrp-app/src/main/java/org/smartregister/view/contract/BaseRegisterContract.java @@ -6,7 +6,6 @@ import org.smartregister.domain.FetchStatus; import java.util.List; -import java.util.Map; /** * Created by keyamn on 27/06/2018. From f3538c87f9fd708e700048c609ea63fd3350e82e Mon Sep 17 00:00:00 2001 From: qiarie Date: Fri, 3 Jul 2020 12:25:19 +0300 Subject: [PATCH 44/70] Add unit tests for - getAllowedLevels - getDefaultLocationLevel - getOpenMrsLocationHierarchy --- .../location/helper/LocationHelperTest.java | 78 +++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/opensrp-app/src/test/java/org/smartregister/location/helper/LocationHelperTest.java b/opensrp-app/src/test/java/org/smartregister/location/helper/LocationHelperTest.java index 5f6b1d131..4cb0d6a73 100644 --- a/opensrp-app/src/test/java/org/smartregister/location/helper/LocationHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/location/helper/LocationHelperTest.java @@ -1,5 +1,7 @@ package org.smartregister.location.helper; +import android.util.Pair; + import net.sqlcipher.database.SQLiteDatabase; import org.junit.After; @@ -11,7 +13,6 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.powermock.reflect.Whitebox; -import android.util.Pair; import org.robolectric.util.ReflectionHelpers; import org.smartregister.AllConstants; import org.smartregister.BaseRobolectricUnitTest; @@ -32,6 +33,7 @@ import java.util.List; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; public class LocationHelperTest extends BaseRobolectricUnitTest { @@ -182,7 +184,6 @@ public void testLocationIdsFromHierarchy() { assertEquals("718b2864-7d6a-44c8-b5b6-bb375f82654e,2c3a0ebd-f79d-4128-a6d3-5dfbffbd01c8", locationIds); } - @Test public void testLocationsFromHierarchyWhenAllowedLevelsContainsReveal() { ReflectionHelpers.setStaticField(LocationHelper.class, "instance", null); @@ -277,7 +278,6 @@ public void testGenerateLocationHierarchyTreeShouldReturnEmptyList() { assertEquals(0, formLocationsList.size()); } - @Test public void testGenerateLocationHierarchyTreeWithMapShouldReturnListWithOtherFormLocationOnly() { locationHelper = Mockito.spy(locationHelper); @@ -298,7 +298,6 @@ public void testGenerateLocationHierarchyTreeWithMapShouldReturnListWithOtherFor assertEquals("", formLocation.level); } - @Test public void testGenerateLocationHierarchyTreeWithMapAndOtherOptionFalseShouldReturnEmptyList() { locationHelper = Mockito.spy(locationHelper); @@ -313,7 +312,6 @@ public void testGenerateLocationHierarchyTreeWithMapAndOtherOptionFalseShouldRet assertEquals(0, formLocationsList.size()); } - @Test public void testGenerateLocationHierarchyTreeWithMapShouldReturnListWithZambiaFormLocation() { locationHelper = Mockito.spy(locationHelper); @@ -424,4 +422,74 @@ public void testGetOpenMrsLocationName() { .when(anmLocationController).get(); assertEquals("Jambula Girls School", locationHelper.getOpenMrsLocationName("982eb3f3-b7e3-450f-a38e-d067f2345212")); } + + public void testGetOpenMrsLocationHierarchyWithEmptyLocationIdShouldReturnEmptyList() { + locationHelper = Mockito.spy(locationHelper); + List hierarchy = locationHelper.getOpenMrsLocationHierarchy("", false); + assertEquals(0, hierarchy.size()); + } + + @Test + public void testGetOpenMrsLocationHierarchyWithLocationIdAndAllowedLevelsFlagShouldReturnListOfLocationNames() { + locationHelper = Mockito.spy(locationHelper); + + ArrayList allowedLevels = new ArrayList<>(); + allowedLevels.add("District"); + allowedLevels.add("Facility"); + allowedLevels.add("Commune"); + + AllSharedPreferences spiedAllSharedPreferences = Mockito.spy((AllSharedPreferences) ReflectionHelpers.getField(locationHelper, "allSharedPreferences")); + ReflectionHelpers.setField(locationHelper, "allSharedPreferences", spiedAllSharedPreferences); + + Mockito.doReturn("Mabebe").when(spiedAllSharedPreferences).fetchRegisteredANM(); + Mockito.doReturn("11").when(spiedAllSharedPreferences).fetchDefaultLocalityId("Mabebe"); + + ANMLocationController anmLocationController = Mockito.spy(CoreLibrary.getInstance().context().anmLocationController()); + ReflectionHelpers.setField(CoreLibrary.getInstance().context(), "anmLocationController", anmLocationController); + + String locationTree = "{\"locationsHierarchy\":{\"map\":{\"1\":{\"id\":\"1\",\"label\":\"Kiamb\",\"node\":{\"locationId\":\"1\",\"name\":\"Kiamb\",\"tags\":[\"District\"],\"voided\":false},\"children\":{\"11\":{\"id\":\"11\",\"label\":\"Mabebe\",\"node\":{\"locationId\":\"11\",\"name\":\"Mabebe\",\"parentLocation\":{\"locationId\":\"1\",\"voided\":false},\"tags\":[\"Commune\"],\"voided\":false},\"children\":{\"111\":{\"id\":\"111\",\"label\":\"Omshindi\",\"node\":{\"locationId\":\"111\",\"name\":\"Omshindi\",\"parentLocation\":{\"locationId\":\"11\",\"voided\":false},\"tags\":[\"District\"],\"voided\":false},\"parent\":\"11\"}},\"parent\":\"1\"}}}},\"parentChildren\":{\"1\":[\"11\"],\"11\":[\"111\"]}}}"; + Mockito.doReturn(locationTree).when(anmLocationController).get(); + + List hierarchy = locationHelper.getOpenMrsLocationHierarchy("1", false); + + assertEquals(1, hierarchy.size()); + assertEquals(true, hierarchy.contains("Kiamb")); + } + + @Test + public void testGetAllowedLevelsReturnsListOfLevelNames() { + ReflectionHelpers.setStaticField(LocationHelper.class, "instance", null); + + ArrayList allowedLevels = new ArrayList<>(); + allowedLevels.add("District"); + allowedLevels.add("Commune"); + allowedLevels.add("Facility"); + + LocationHelper.init(allowedLevels, "Facility"); + locationHelper = LocationHelper.getInstance(); + + List actualAllowedLevels = locationHelper.getAllowedLevels(); + + assertNotNull(actualAllowedLevels); + assertEquals(3, actualAllowedLevels.size()); + assertEquals(true, actualAllowedLevels.contains("Facility")); + } + + @Test + public void testGetDefaultLocationLevelReturnsLocationLevelName() { + ReflectionHelpers.setStaticField(LocationHelper.class, "instance", null); + + ArrayList allowedLevels = new ArrayList<>(); + allowedLevels.add("District"); + allowedLevels.add("Commune"); + allowedLevels.add("Facility"); + + LocationHelper.init(allowedLevels, "Facility"); + locationHelper = LocationHelper.getInstance(); + + String defaultLocationLevel = locationHelper.getDefaultLocationLevel(); + + assertEquals("Facility", defaultLocationLevel); + } + } From 4e64270da54c0d6d492654f2390f2fcbaf7a176e Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 7 Jul 2020 10:52:37 +0300 Subject: [PATCH 45/70] Fix failing build - Fix failing tests --- opensrp-app/build.gradle | 43 +++++++++++++------ .../security/SecurityHelperTest.java | 5 +-- .../view/activity/BaseLoginActivityTest.java | 6 +-- .../mock/BaseRegisterActivityMock.java | 11 +++++ 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/opensrp-app/build.gradle b/opensrp-app/build.gradle index 1d43df1f3..82eb74095 100644 --- a/opensrp-app/build.gradle +++ b/opensrp-app/build.gradle @@ -206,22 +206,38 @@ dependencies { implementation 'org.smartregister:opensrp-client-utils:0.0.2-SNAPSHOT' implementation 'org.smartregister:opensrp-plan-evaluator:0.0.19-SNAPSHOT' + implementation('ch.acra:acra:4.5.0') { + exclude group: 'org.json', module: 'json' + } - implementation 'xerces:xercesImpl:2.12.0' - implementation('org.smartregister:opensrp-client-native-form:1.9.2-SNAPSHOT@aar') { - transitive = true - exclude group: 'id.zelory', module: 'compressor' + implementation 'com.github.ybq:Android-SpinKit:1.2.0' + implementation 'com.mcxiaoke.volley:library:1.0.19' + + implementation fileTree(include: ['*.jar'], dir: 'libs') + annotationProcessor fileTree(include: ['butterknife*.jar'], dir: 'libs') + + implementation 'com.cloudant:cloudant-http:2.7.0' + implementation 'com.android.support:recyclerview-v7:28.0.0' + + implementation('com.android.support:design:28.0.0') { exclude group: 'com.android.support', module: 'recyclerview-v7' - exclude group: 'com.android.support', module: 'appcompat-v7' - exclude group: 'com.android.support', module: 'cardview-v7' - exclude group: 'com.android.support', module: 'support-media-compat' + } + + implementation 'com.evernote:android-job:1.2.6' + implementation group: 'commons-validator', name: 'commons-validator', version: '1.6' + implementation 'de.hdodenhof:circleimageview:2.2.0' + + implementation('org.smartregister:android-p2p-sync:0.3.6-SNAPSHOT') { exclude group: 'com.android.support', module: 'support-v4' - exclude group: 'com.android.support', module: 'design' - exclude group: 'org.yaml', module: 'snakeyaml' - exclude group: 'io.ona.rdt-capture', module: 'lib' - exclude group: 'com.github.johnkil.print', module: 'print' - exclude group: 'com.android.support', module: 'multidex' + exclude group: 'com.android.support', module: 'appcompat-v7' + exclude group: 'android.arch.core', module: 'runtime' } + + implementation 'org.smartregister:opensrp-client-utils:0.0.2-SNAPSHOT' + + implementation 'org.smartregister:opensrp-plan-evaluator:0.0.13-SNAPSHOT' + + implementation 'xerces:xercesImpl:2.12.0' } dependencies { @@ -229,7 +245,6 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') androidTestImplementation 'junit:junit:4.12' - testImplementation 'com.android.support:multidex:1.0.0' testImplementation group: 'com.google.android', name: 'android-test', version: '4.1.1.4' testImplementation 'org.apache.maven:maven-ant-tasks:2.1.3' testImplementation 'org.mockito:mockito-core:1.9.5' @@ -296,4 +311,4 @@ coveralls { sourceDirs = ["$project.projectDir/src/main/java"] } -apply from: '../maven.gradle' +apply from: '../maven.gradle' \ No newline at end of file diff --git a/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java b/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java index afc6b6998..d627c22c3 100644 --- a/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java @@ -40,9 +40,6 @@ public class SecurityHelperTest { @Mock private SecretKey secretKey; - @Mock - private PBEKeySpec keySpec; - private char[] TEST_PASSWORD; private static final String TEST_DATA = "Some Random Test Data"; @@ -162,4 +159,4 @@ public void testGetPsswordHashReturnsHashedPasswordObject() throws Exception { Assert.assertNotNull(passwordHash.getPassword()); Assert.assertNotNull(passwordHash.getSalt()); } -} \ No newline at end of file +} diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/BaseLoginActivityTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/BaseLoginActivityTest.java index 1b11d6c3b..27ddf9eb8 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/BaseLoginActivityTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/BaseLoginActivityTest.java @@ -99,7 +99,7 @@ public void onClickLoginShouldCallAttemptLogin() { Mockito.doReturn(R.id.login_login_btn).when(view).getId(); baseLoginActivity.onClick(view); - Mockito.verify(baseLoginActivity.mLoginPresenter).attemptLogin(Mockito.anyString(), Mockito.anyString()); + Mockito.verify(baseLoginActivity.mLoginPresenter).attemptLogin(Mockito.anyString(), Mockito.any(char[].class)); } @@ -109,7 +109,7 @@ public void onEditorActionShouldShouldCallAttemptLoginAndReturnTrueWhenActionIsD Mockito.doReturn(R.id.login_login_btn).when(view).getId(); Assert.assertTrue(baseLoginActivity.onEditorAction(null, EditorInfo.IME_ACTION_DONE, null)); - Mockito.verify(baseLoginActivity.mLoginPresenter).attemptLogin(Mockito.anyString(), Mockito.anyString()); + Mockito.verify(baseLoginActivity.mLoginPresenter).attemptLogin(Mockito.anyString(), Mockito.any(char[].class)); } @Test @@ -166,7 +166,7 @@ public void setPasswordErrorShouldCallSetErrorAndShowErrorDialog() { @Test public void isAppVersionAllowedShouldReturnSyncUtilsValue() throws PackageManager.NameNotFoundException { - SyncUtils syncUtils = Mockito.spy((SyncUtils) ReflectionHelpers.getField(baseLoginActivity, "syncUtils")); + SyncUtils syncUtils = Mockito.spy((SyncUtils) ReflectionHelpers.getField(baseLoginActivity, "syncUtils")); ReflectionHelpers.setField(baseLoginActivity, "syncUtils", syncUtils); Mockito.doReturn(false).when(syncUtils).isAppVersionAllowed(); diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/mock/BaseRegisterActivityMock.java b/opensrp-app/src/test/java/org/smartregister/view/activity/mock/BaseRegisterActivityMock.java index 994b25921..4f9ec2e17 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/mock/BaseRegisterActivityMock.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/mock/BaseRegisterActivityMock.java @@ -14,6 +14,7 @@ import java.util.HashMap; import java.util.List; +import java.util.Map; import static org.mockito.Mockito.mock; @@ -43,6 +44,11 @@ protected Fragment[] getOtherFragments() { return new Fragment[0]; } + @Override + public void startFormActivity(String formName, String entityId, Map metaData) { + // mock do nothing + } + @Override public void startFormActivity(String formName, String entityId, String metaData) { //mock do nothing @@ -68,6 +74,11 @@ public void startRegistration() { //mock do nothing } + @Override + public void onPointerCaptureChanged(boolean hasCapture) { + + } + public static class BaseRegisterFragmentMock extends BaseRegisterFragment { From 4ecdc70c5e12a39bd933e083c1563c4721c7f6be Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 7 Jul 2020 15:04:26 +0300 Subject: [PATCH 46/70] Codacy clean up --- .../view/activity/mock/BaseRegisterActivityMock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/mock/BaseRegisterActivityMock.java b/opensrp-app/src/test/java/org/smartregister/view/activity/mock/BaseRegisterActivityMock.java index 4f9ec2e17..d61a59876 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/mock/BaseRegisterActivityMock.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/mock/BaseRegisterActivityMock.java @@ -76,7 +76,7 @@ public void startRegistration() { @Override public void onPointerCaptureChanged(boolean hasCapture) { - + //mock do nothing } public static class BaseRegisterFragmentMock extends BaseRegisterFragment { From e7c511bce976962b8e0d01c601a12e87ed8cb0c2 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 7 Jul 2020 17:24:23 +0300 Subject: [PATCH 47/70] Fix failing build - Fix unit tests - Downgrade travis config to Android API 27 --- .travis.yml | 6 +++--- .../src/test/java/org/smartregister/BaseUnitTest.java | 2 +- .../smartregister/view/activity/DrishtiApplicationTest.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 64aefe376..d3dada302 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,9 +35,9 @@ before_install: - mkdir -p $HOME/.android && touch $HOME/.android/repositories.cfg install: # accept licenses for all available packages that have not already been accepted - - yes | sdkmanager "platforms;android-28" + - yes | sdkmanager "platforms;android-27" - yes | sdkmanager --licenses >/dev/null - - yes | sdkmanager "platforms;android-28" + - yes | sdkmanager "platforms;android-27" before_script: - echo "Travis branch is $TRAVIS_BRANCH" - echo "Travis branch is in pull request $TRAVIS_PULL+REQUEST" @@ -70,4 +70,4 @@ deploy: provider: script script: ./gradlew :opensrp-app:uploadArchives -PmavenLocal=false on: - tags: true \ No newline at end of file + tags: true diff --git a/opensrp-app/src/test/java/org/smartregister/BaseUnitTest.java b/opensrp-app/src/test/java/org/smartregister/BaseUnitTest.java index b5487690e..c1039a923 100644 --- a/opensrp-app/src/test/java/org/smartregister/BaseUnitTest.java +++ b/opensrp-app/src/test/java/org/smartregister/BaseUnitTest.java @@ -21,7 +21,7 @@ */ @RunWith(RobolectricTestRunner.class) -@Config(application = TestApplication.class, shadows = {FontTextViewShadow.class, ShadowDrawableResourcesImpl.class}, sdk = Build.VERSION_CODES.P) +@Config(application = TestApplication.class, shadows = {FontTextViewShadow.class, ShadowDrawableResourcesImpl.class}, sdk = Build.VERSION_CODES.O_MR1) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java index 84dc2faa8..df7313f5a 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java @@ -71,7 +71,7 @@ public void getRepository() { @Test public void getPassword() { String username = "anm"; - String password = "pwd"; + char[] password = "pwd".toCharArray(); drishtiApplication.onCreate(); From f2632c19442feb4f203859bd97fa52da4a3e2a0b Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Wed, 15 Jul 2020 21:20:45 +0300 Subject: [PATCH 48/70] Fix bug :bug: - Fix bug with verify authorization causing premature return when processing --- .../src/main/java/org/smartregister/service/HTTPAgent.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index 1f8569af7..16f1d1f38 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -834,6 +834,11 @@ public boolean verifyAuthorization() { String userInfoUrl = allSharedPreferences.getPreferences().getString(AccountHelper.CONFIGURATION_CONSTANTS.USERINFO_ENDPOINT_URL, ""); + if (StringUtils.isBlank(userInfoUrl)) { + + return verifyAuthorizationLegacy(); + } + HttpURLConnection urlConnection = null; InputStream inputStream = null; @@ -879,7 +884,9 @@ public boolean verifyAuthorization() { } } catch (IOException e) { + Timber.e(e); + } finally { closeConnection(urlConnection); From e2ae820c03a52d4b063b0d001a70853a7da0ab95 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Thu, 16 Jul 2020 18:10:11 +0300 Subject: [PATCH 49/70] Authentication with oauth2 credentials - Add conditional for sending client id and secret depending on whether keycloak is configured or not - Fix HTTPAgent unit tests --- .../smartregister/account/AccountHelper.java | 1 + .../login/task/RemoteLoginTask.java | 11 ++-- .../org/smartregister/service/HTTPAgent.java | 14 +++- .../smartregister/service/HTTPAgentTest.java | 64 +++++++++++++++++-- 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java index 9ebf9cb37..71ec29ea6 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java @@ -21,6 +21,7 @@ public class AccountHelper { public static final class CONFIGURATION_CONSTANTS { + public static final String IS_KEYCLOAK_CONFIGURED = "is_keycloack_configured"; public final static String TOKEN_ENDPOINT_URL = "token_endpoint_url"; public final static String AUTHORIZATION_ENDPOINT_URL = "authorization_endpoint_url"; public final static String ISSUER_ENDPOINT_URL = "issuer_endpoint_url"; diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index 2050ca72d..005f7f817 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -67,6 +67,12 @@ protected LoginResponse doInBackground(Void... params) { boolean isKeycloakConfigured = accountConfiguration != null; + //Persist config resources + SharedPreferences.Editor sharedPrefEditor = CoreLibrary.getInstance().context().allSharedPreferences().getPreferences().edit(); + sharedPrefEditor.putBoolean(AccountHelper.CONFIGURATION_CONSTANTS.IS_KEYCLOAK_CONFIGURED, isKeycloakConfigured); + sharedPrefEditor.apply(); + + if (!isKeycloakConfigured) { accountConfiguration = new AccountConfiguration(); accountConfiguration.setGrantTypesSupported(Arrays.asList(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD)); @@ -80,9 +86,6 @@ protected LoginResponse doInBackground(Void... params) { if (!accountConfiguration.getGrantTypesSupported().contains(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD)) throw new AccountsException("OAuth configuration DOES NOT support the Password Grant Type"); - //Persist config resources - SharedPreferences.Editor sharedPrefEditor = CoreLibrary.getInstance().context().allSharedPreferences().getPreferences().edit(); - sharedPrefEditor.putString(AccountHelper.CONFIGURATION_CONSTANTS.TOKEN_ENDPOINT_URL, accountConfiguration.getTokenEndpoint()); sharedPrefEditor.putString(AccountHelper.CONFIGURATION_CONSTANTS.AUTHORIZATION_ENDPOINT_URL, accountConfiguration.getAuthorizationEndpoint()); sharedPrefEditor.putString(AccountHelper.CONFIGURATION_CONSTANTS.ISSUER_ENDPOINT_URL, accountConfiguration.getIssuerEndpoint()); @@ -120,7 +123,7 @@ protected LoginResponse doInBackground(Void... params) { SyncSettingsServiceHelper syncSettingsServiceHelper = new SyncSettingsServiceHelper(getOpenSRPContext().configuration().dristhiBaseURL(), getOpenSRPContext().getHttpAgent()); try { - JSONArray settings = syncSettingsServiceHelper.pullSettingsFromServer(Utils.getFilterValue(loginResponse, CoreLibrary.getInstance().getSyncConfiguration().getSyncFilterParam()), response.getAccessToken()); + JSONArray settings = pullSetting(syncSettingsServiceHelper, loginResponse, response.getAccessToken()); JSONObject prefSettingsData = new JSONObject(); prefSettingsData.put(AllConstants.PREF_KEY.SETTINGS, settings); diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index 16f1d1f38..40c43700e 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -579,8 +579,17 @@ public AccountResponse oauth2authenticateCore(StringBuffer requestParamBuffer, S final String base64Auth = BaseEncoding.base64().encode(new StringBuffer(clientId).append(':').append(clientSecret).toString().getBytes(CharEncoding.UTF_8)); requestParamBuffer.append("&grant_type=").append(grantType); - requestParamBuffer.append("&client_id=").append(clientId); - requestParamBuffer.append("&client_secret=").append(clientSecret); + + if (allSharedPreferences.getPreferences().getBoolean(AccountHelper.CONFIGURATION_CONSTANTS.IS_KEYCLOAK_CONFIGURED, false)) { + + requestParamBuffer.append("&client_id=").append(clientId); + requestParamBuffer.append("&client_secret=").append(clientSecret); + + } else { + + urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + base64Auth); + + } byte[] postData = requestParamBuffer.toString().getBytes(CharEncoding.UTF_8); int postDataLength = postData.length; @@ -592,7 +601,6 @@ public AccountResponse oauth2authenticateCore(StringBuffer requestParamBuffer, S urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); urlConnection.setRequestProperty("charset", "utf-8"); urlConnection.setRequestProperty("Content-Length", Integer.toString(postDataLength)); - urlConnection.setRequestProperty(AllConstants.HTTP_REQUEST_HEADERS.AUTHORIZATION, AllConstants.HTTP_REQUEST_AUTH_TOKEN_TYPE.BASIC + " " + base64Auth); urlConnection.setUseCaches(false); outputStream = urlConnection.getOutputStream(); diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index 85849f16a..a20c03758 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -178,7 +178,8 @@ public void setUp() { Mockito.doReturn(1).when(syncConfiguration).getMaxAuthenticationRetries(); Mockito.doReturn(TEST_USERNAME).when(allSharedPreferences).fetchRegisteredANM(); - Mockito.doReturn(TEST_USERNAME).when(allSharedPreferences).fetchRegisteredANM(); + Mockito.doReturn(sharedPreferences).when(allSharedPreferences).getPreferences(); + Mockito.doReturn(true).when(sharedPreferences).getBoolean(AccountHelper.CONFIGURATION_CONSTANTS.IS_KEYCLOAK_CONFIGURED, false); PowerMockito.mockStatic(AccountHelper.class); PowerMockito.when(AccountHelper.getOauthAccountByNameAndType(TEST_USERNAME, accountAuthenticatorXml.getAccountType())).thenReturn(account); @@ -286,7 +287,9 @@ public void testPostWithJsonResponse() { @Test - public void testOauth2authenticateCreatesUrlConnectionWithCorrectParameters() throws Exception { + public void testOauth2authenticateCreatesUrlConnectionWithCorrectParametersWhenKeycloakNotConfigured() throws Exception { + + Mockito.doReturn(false).when(sharedPreferences).getBoolean(AccountHelper.CONFIGURATION_CONSTANTS.IS_KEYCLOAK_CONFIGURED, false); URL url = PowerMockito.mock(URL.class); Assert.assertNotNull(url); @@ -320,9 +323,11 @@ public void testOauth2authenticateCreatesUrlConnectionWithCorrectParameters() th Mockito.verify(httpURLConnection).setConnectTimeout(60000); Mockito.verify(httpURLConnection).setReadTimeout(60000); - String requestParams = "&grant_type=" + AccountHelper.OAUTH.GRANT_TYPE.PASSWORD + "&username=" + TEST_USERNAME + "&password=" + String.valueOf(TEST_PASSWORD) + "&client_id=" + TEST_CLIENT_ID + "&client_secret=" + TEST_CLIENT_SECRET; + String requestParams = "&grant_type=" + AccountHelper.OAUTH.GRANT_TYPE.PASSWORD + "&username=" + TEST_USERNAME + "&password=" + String.valueOf(TEST_PASSWORD); + ArgumentCaptor paramLengthCaptor = ArgumentCaptor.forClass(Integer.class); + Mockito.verify(httpURLConnection).setFixedLengthStreamingMode(paramLengthCaptor.capture()); + Assert.assertEquals((Integer) requestParams.getBytes(CharEncoding.UTF_8).length, paramLengthCaptor.getValue()); - Mockito.verify(httpURLConnection).setFixedLengthStreamingMode(requestParams.getBytes(CharEncoding.UTF_8).length); Mockito.verify(httpURLConnection).setDoOutput(true); Mockito.verify(httpURLConnection).setInstanceFollowRedirects(false); Mockito.verify(httpURLConnection).setRequestMethod("POST"); @@ -336,6 +341,57 @@ public void testOauth2authenticateCreatesUrlConnectionWithCorrectParameters() th } + @Test + public void testOauth2authenticateCreatesUrlConnectionWithCorrectParametersWhenKeycloakConfigured() throws Exception { + + URL url = PowerMockito.mock(URL.class); + Assert.assertNotNull(url); + + HTTPAgent httpAgentSpy = Mockito.spy(httpAgent); + + Mockito.doReturn(httpURLConnection).when(httpAgentSpy).getHttpURLConnection(TEST_TOKEN_ENDPOINT); + Mockito.doReturn(TEST_CLIENT_ID).when(syncConfiguration).getOauthClientId(); + Mockito.doReturn(TEST_CLIENT_SECRET).when(syncConfiguration).getOauthClientSecret(); + + Mockito.doReturn(outputStream).when(httpURLConnection).getOutputStream(); + Mockito.doReturn(inputStream).when(httpURLConnection).getInputStream(); + Mockito.doReturn(HttpURLConnection.HTTP_OK).when(httpURLConnection).getResponseCode(); + + PowerMockito.mockStatic(IOUtils.class); + PowerMockito.when(IOUtils.toString(inputStream)).thenReturn(TOKEN_REQUEST_SERVER_RESPONSE); + + + AccountResponse accountResponse = httpAgentSpy.oauth2authenticate(TEST_USERNAME, TEST_PASSWORD, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, TEST_TOKEN_ENDPOINT); + + Assert.assertNotNull(accountResponse); + Assert.assertEquals(200, accountResponse.getStatus()); + Assert.assertEquals("1r9A8zi5E3r@Zz", accountResponse.getAccessToken()); + Assert.assertEquals("bearer", accountResponse.getTokenType()); + Assert.assertEquals("text_token", accountResponse.getRefreshToken()); + Assert.assertEquals(Integer.valueOf("3600"), accountResponse.getExpiresIn()); + Assert.assertEquals(Integer.valueOf("36000"), accountResponse.getRefreshExpiresIn()); + Assert.assertEquals("read write trust", accountResponse.getScope()); + + + Mockito.verify(httpURLConnection).setConnectTimeout(60000); + Mockito.verify(httpURLConnection).setReadTimeout(60000); + + String requestParams = "&grant_type=" + AccountHelper.OAUTH.GRANT_TYPE.PASSWORD + "&username=" + TEST_USERNAME + "&password=" + String.valueOf(TEST_PASSWORD) + "&client_id=" + TEST_CLIENT_ID + "&client_secret=" + TEST_CLIENT_SECRET; + ArgumentCaptor paramLengthCaptor = ArgumentCaptor.forClass(Integer.class); + Mockito.verify(httpURLConnection).setFixedLengthStreamingMode(paramLengthCaptor.capture()); + Assert.assertEquals((Integer) requestParams.getBytes(CharEncoding.UTF_8).length, paramLengthCaptor.getValue()); + + Mockito.verify(httpURLConnection).setDoOutput(true); + Mockito.verify(httpURLConnection).setInstanceFollowRedirects(false); + Mockito.verify(httpURLConnection).setRequestMethod("POST"); + Mockito.verify(httpURLConnection).setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + Mockito.verify(httpURLConnection).setRequestProperty("charset", "utf-8"); + Mockito.verify(httpURLConnection).setRequestProperty(ArgumentMatchers.eq("Content-Length"), ArgumentMatchers.anyString()); + Mockito.verify(httpURLConnection).setUseCaches(false); + Mockito.verify(httpURLConnection).setInstanceFollowRedirects(false); + + } + @Test public void testOauth2authenticateReturnsCorrectResponseForBadRequest() throws Exception { From 366ecc9ecba2376fa3510604733d7e9ed54cbd10 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Thu, 16 Jul 2020 19:13:12 +0300 Subject: [PATCH 50/70] Login Activity clean up - Remove deprecated LoginActivity class and dependencies --- .../view/activity/LoginActivityTest.java | 68 ---- .../view/activity/LoginActivity.java | 293 ------------------ .../view/fragment/SecuredFragment.java | 10 +- .../view/activity/LoginActivityTest.java | 141 --------- .../LoginActivityWithRemoteLoginTest.java | 145 --------- .../view/activity/mock/LoginActivityMock.java | 42 --- .../view/fragment/SecuredFragmentTest.java | 17 - 7 files changed, 3 insertions(+), 713 deletions(-) delete mode 100644 opensrp-app/src/androidTest/java/org/smartregister/view/activity/LoginActivityTest.java delete mode 100644 opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java delete mode 100644 opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityTest.java delete mode 100644 opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityWithRemoteLoginTest.java delete mode 100644 opensrp-app/src/test/java/org/smartregister/view/activity/mock/LoginActivityMock.java diff --git a/opensrp-app/src/androidTest/java/org/smartregister/view/activity/LoginActivityTest.java b/opensrp-app/src/androidTest/java/org/smartregister/view/activity/LoginActivityTest.java deleted file mode 100644 index 71d4263ff..000000000 --- a/opensrp-app/src/androidTest/java/org/smartregister/view/activity/LoginActivityTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.smartregister.view.activity; - -import static org.smartregister.util.Wait.waitForFilteringToFinish; - -public class LoginActivityTest { -// private DrishtiSolo solo; -// private FakeUserService userService; - - public LoginActivityTest() { -// super(LoginActivity.class); - } - - // @Override - protected void setUp() throws Exception { -// FakeDrishtiService drishtiService = new FakeDrishtiService(String.valueOf(new Date().getTime() - 1)); -// userService = new FakeUserService(); -// -// setupService(drishtiService, userService, -1000).updateApplicationContext(getActivity()); -// CoreLibrary.getInstance().context().session().setPassword(null); -// -// solo = new DrishtiSolo(getInstrumentation(), getActivity()); - } - - public void ignoreTestShouldAllowLoginWithoutCheckingRemoteLoginWhenLocalLoginSucceeds() throws Exception { -// userService.setupFor("user", "password", true, true, UNKNOWN_RESPONSE); -// -// solo.assertCanLogin("user", "password"); -// -// userService.assertOrderOfCalls("local", "login"); - } - - public void ignoreTestShouldTryRemoteLoginWhenThereIsNoRegisteredUser() throws Exception { -// userService.setupFor("user", "password", false, false, SUCCESS); -// -// solo.assertCanLogin("user", "password"); -// -// userService.assertOrderOfCalls("remote", "login"); - } - - public void ignoreTestShouldFailToLoginWhenBothLoginMethodsFail() throws Exception { -// userService.setupFor("user", "password", false, false, UNKNOWN_RESPONSE); -// -// solo.assertCannotLogin("user", "password"); -// -// userService.assertOrderOfCalls("remote"); - } - - public void ignoreTestShouldNotTryRemoteLoginWhenRegisteredUserExistsEvenIfLocalLoginFails() throws Exception { -// userService.setupFor("user", "password", true, false, SUCCESS); -// -// solo.assertCannotLogin("user", "password"); -// userService.assertOrderOfCalls("local"); - } - - public void ignoreTestShouldNotTryLocalLoginWhenRegisteredUserDoesNotExist() throws Exception { -// userService.setupFor("user", "password", false, true, SUCCESS); -// -// solo.assertCanLogin("user", "password"); -// userService.assertOrderOfCalls("remote", "login"); - } - - // @Override - public void tearDown() throws Exception { - waitForFilteringToFinish(); -// waitForProgressBarToGoAway(getActivity()); -// solo.finishOpenedActivities(); - } -} diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java deleted file mode 100644 index ddee8f121..000000000 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/LoginActivity.java +++ /dev/null @@ -1,293 +0,0 @@ -package org.smartregister.view.activity; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.Button; -import android.widget.EditText; -import android.widget.TextView; - -import org.smartregister.Context; -import org.smartregister.CoreLibrary; -import org.smartregister.R; -import org.smartregister.domain.LoginResponse; -import org.smartregister.domain.jsonmapping.LoginResponseData; -import org.smartregister.event.Listener; -import org.smartregister.security.SecurityHelper; -import org.smartregister.sync.DrishtiSyncScheduler; -import org.smartregister.util.LangUtils; -import org.smartregister.util.SyncUtils; -import org.smartregister.util.Utils; -import org.smartregister.view.BackgroundAction; -import org.smartregister.view.LockingBackgroundTask; -import org.smartregister.view.ProgressIndicator; - -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import timber.log.Timber; - -import static android.view.inputmethod.InputMethodManager.HIDE_NOT_ALWAYS; -import static org.smartregister.domain.LoginResponse.SUCCESS; -import static org.smartregister.util.Log.logError; -import static org.smartregister.util.Log.logVerbose; - -public class LoginActivity extends Activity implements View.OnClickListener { - private Context context; - private EditText userNameEditText; - private EditText passwordEditText; - private ProgressDialog progressDialog; - private Button loginButton; - private SyncUtils syncUtils; - - @Override - protected void attachBaseContext(android.content.Context base) { - // get language from prefs - String lang = LangUtils.getLanguage(base.getApplicationContext()); - super.attachBaseContext(LangUtils.setAppLocale(base, lang)); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - logVerbose("Initializing ..."); - setContentView(R.layout.login); - - context = CoreLibrary.getInstance().context().updateApplicationContext(this.getApplicationContext()); - syncUtils = new SyncUtils(this); - initializeLoginFields(); - initializeBuildDetails(); - setDoneActionHandlerOnPasswordField(); - initializeProgressDialog(); - - - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add("Settings"); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getTitle().toString().equalsIgnoreCase("Settings")) { - startActivity(new Intent(this, SettingsActivity.class)); - return true; - } - return super.onOptionsItemSelected(item); - } - - private void initializeBuildDetails() { - TextView buildDetailsTextView = findViewById(R.id.login_build); - try { - buildDetailsTextView.setText(String.format(getString(R.string.app_version), Utils.getVersion(this - .getApplicationContext()), Utils.getBuildDate(true))); - } catch (Exception e) { - logError("Error fetching build details: " + e); - } - } - - @Override - protected void onResume() { - super.onResume(); - - if (!context.IsUserLoggedOut()) { - goToHome(); - } - - fillUserIfExists(); - } - - public void login(final View view) { - hideKeyboard(); - view.setClickable(false); - - final String userName = userNameEditText.getText().toString(); - final char[] password = SecurityHelper.readValue(passwordEditText.getText()); - - if (context.userService().hasARegisteredUser()) { - localLogin(view, userName, password); - } else { - remoteLogin(view, userName, password); - } - } - - private void initializeLoginFields() { - userNameEditText = findViewById(R.id.login_userNameText); - passwordEditText = findViewById(R.id.login_passwordText); - loginButton = findViewById(R.id.login_loginButton); - loginButton.setOnClickListener(this); - } - - private void setDoneActionHandlerOnPasswordField() { - passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_DONE) { - login(loginButton); - } - return false; - } - }); - } - - private void initializeProgressDialog() { - progressDialog = new ProgressDialog(this); - progressDialog.setCancelable(false); - progressDialog.setTitle(getString(R.string.loggin_in_dialog_title)); - progressDialog.setMessage(getString(R.string.loggin_in_dialog_message)); - } - - private void localLogin(View view, String userName, char[] password) { - try { - if (!syncUtils.isAppVersionAllowed()) { - showErrorDialog(getString(R.string.outdated_app)); - return; - } - - if (context.userService().isValidLocalLogin(userName, password)) { - localLoginWith(userName, password); - } else { - showErrorDialog(getString(R.string.login_failed_dialog_message)); - view.setClickable(true); - } - } catch (PackageManager.NameNotFoundException e) { - Timber.e(e); - } - } - - - private void remoteLogin(final View view, final String userName, final char[] password) { - - try { - if (!syncUtils.isAppVersionAllowed()) { - showErrorDialog(getString(R.string.outdated_app)); - return; - } - - if (!context.allSharedPreferences().fetchBaseURL("").isEmpty()) { - - tryRemoteLogin(userName, password, new Listener() { - public void onEvent(LoginResponse loginResponse) { - if (loginResponse == SUCCESS) { - remoteLoginWith(userName, password, loginResponse.payload()); - } else { - if (loginResponse == null) { - showErrorDialog("Login failed. Unknown reason. Try Again"); - } else { - showErrorDialog(loginResponse.message()); - } - view.setClickable(true); - } - } - }); - - } else { - view.setClickable(true); - showErrorDialog("OpenSRP Base URL is missing, Please add it in Settings and try again"); - } - } catch (PackageManager.NameNotFoundException e) { - Timber.e(e); - } - } - - private void showErrorDialog(String message) { - AlertDialog dialog = new AlertDialog.Builder(this) - .setTitle(getString(R.string.login_failed_dialog_title)).setMessage(message) - .setPositiveButton("OK", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - } - }).create(); - dialog.show(); - } - - private void tryRemoteLogin(final String userName, final char[] password, final - Listener afterLoginCheck) { - LockingBackgroundTask task = new LockingBackgroundTask(new ProgressIndicator() { - @Override - public void setVisible() { - progressDialog.show(); - } - - @Override - public void setInvisible() { - progressDialog.dismiss(); - } - }); - - task.doActionInBackground(new BackgroundAction() { - public LoginResponse actionToDoInBackgroundThread() { - return context.userService().isValidRemoteLogin(userName, password); - } - - public void postExecuteInUIThread(LoginResponse result) { - afterLoginCheck.onEvent(result); - } - }); - } - - private void fillUserIfExists() { - if (context.userService().hasARegisteredUser()) { - userNameEditText.setText(context.allSharedPreferences().fetchRegisteredANM()); - userNameEditText.setEnabled(false); - } - } - - private void hideKeyboard() { - InputMethodManager inputManager = (InputMethodManager) getSystemService( - INPUT_METHOD_SERVICE); - inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), HIDE_NOT_ALWAYS); - } - - private void localLoginWith(String userName, char[] password) { - context.userService().localLogin(userName, password); - goToHome(); - DrishtiSyncScheduler.startOnlyIfConnectedToNetwork(getApplicationContext()); - } - - private void remoteLoginWith(String userName, char[] password, LoginResponseData userInfo) { - context.userService().processLoginResponseDataForUser(userName, password, userInfo); - goToHome(); - DrishtiSyncScheduler.startOnlyIfConnectedToNetwork(getApplicationContext()); - } - - protected void goToHome() { - startActivity(new Intent(this, NativeHomeActivity.class)); - finish(); - } - - private String getVersion() throws PackageManager.NameNotFoundException { - PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); - return packageInfo.versionName; - } - - private String getBuildDate() throws PackageManager.NameNotFoundException, IOException { - ApplicationInfo applicationInfo = getPackageManager() - .getApplicationInfo(getPackageName(), 0); - ZipFile zf = new ZipFile(applicationInfo.sourceDir); - ZipEntry ze = zf.getEntry("classes.dex"); - return new SimpleDateFormat("dd MMM yyyy", Utils.getDefaultLocale()) - .format(new java.util.Date(ze.getTime())); - } - - @Override - public void onClick(View view) { - login(view); - } -} diff --git a/opensrp-app/src/main/java/org/smartregister/view/fragment/SecuredFragment.java b/opensrp-app/src/main/java/org/smartregister/view/fragment/SecuredFragment.java index 3eadfe51f..3ad371a7b 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/fragment/SecuredFragment.java +++ b/opensrp-app/src/main/java/org/smartregister/view/fragment/SecuredFragment.java @@ -18,7 +18,6 @@ import org.smartregister.util.Utils; import org.smartregister.view.activity.DrishtiApplication; import org.smartregister.view.activity.FormActivity; -import org.smartregister.view.activity.LoginActivity; import org.smartregister.view.activity.MicroFormActivity; import org.smartregister.view.activity.SecuredActivity; import org.smartregister.view.controller.ANMController; @@ -44,11 +43,9 @@ public abstract class SecuredFragment extends Fragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - logoutListener = new Listener() { - public void onEvent(Boolean data) { - if (getActivity() != null && !getActivity().isFinishing()) { - getActivity().finish(); - } + logoutListener = data -> { + if (getActivity() != null && !getActivity().isFinishing()) { + getActivity().finish(); } }; Event.ON_LOGOUT.addListener(logoutListener); @@ -104,7 +101,6 @@ public boolean onOptionsItemSelected(MenuItem item) { public void logoutUser() { context().userService().logout(); - startActivity(new Intent(getActivity(), LoginActivity.class)); } protected abstract void onCreation(); diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityTest.java deleted file mode 100644 index 624f3de9d..000000000 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityTest.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.smartregister.view.activity; - -import android.app.AlarmManager; -import android.content.Intent; -import android.os.IBinder; -import android.util.Log; -import android.view.inputmethod.InputMethodManager; -import android.widget.Button; -import android.widget.EditText; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.reflect.Whitebox; -import org.robolectric.Robolectric; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.android.controller.ActivityController; -import org.robolectric.annotation.Config; -import org.smartregister.BaseUnitTest; -import org.smartregister.CoreLibrary; -import org.smartregister.R; -import org.smartregister.customshadows.AndroidTreeViewShadow; -import org.smartregister.customshadows.FontTextViewShadow; -import org.smartregister.repository.AllSharedPreferences; -import org.smartregister.service.UserService; -import org.smartregister.shadows.AlarmManagerShadow; -import org.smartregister.shadows.PendingIntentShadow; -import org.smartregister.shadows.ShadowContext; -import org.smartregister.sync.DrishtiSyncScheduler; -import org.smartregister.view.activity.mock.LoginActivityMock; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.isNull; - -/** - * Created by kaderchowdhury on 11/11/17. - */ -@PowerMockIgnore({"javax.xml.*", "org.xml.sax.*", "org.w3c.dom.*", "org.springframework.context.*", "org.apache.log4j.*"}) -@PrepareForTest({CoreLibrary.class}) -@Config(shadows = {ShadowContext.class, FontTextViewShadow.class, AndroidTreeViewShadow.class, PendingIntentShadow.class, AlarmManagerShadow.class}) -public class LoginActivityTest extends BaseUnitTest { - - private ActivityController controller; - - @Mock - private android.content.Context applicationContext; - - @Mock - private AllSharedPreferences allSharedPreferences; - - @Mock - private InputMethodManager inputManager; - - @Mock - private AlarmManager alarmManager; - - @InjectMocks - private LoginActivityMock activity; - - @Mock - private org.smartregister.Context context_; - - @Mock - private UserService userService; - - @Before - public void setUp() throws Exception { - org.mockito.MockitoAnnotations.initMocks(this); - CoreLibrary.init(context_); - LoginActivityMock.inputManager = inputManager; -// ShadowSystemClock shadowClock = new ShadowSystemClock(); -// shadowClock.setCurrentTimeMillis(142436987); - - DrishtiSyncScheduler.setReceiverClass(LoginActivityMock.class); - -// Context context = CoreLibrary.getInstance().context().updateApplicationContext(activity.getApplicationContext()); -// this.context_ = context; - Mockito.doReturn(applicationContext).when(context_).applicationContext(); - Mockito.doReturn(context_).when(context_).updateApplicationContext(any(android.content.Context.class)); - Mockito.doReturn(userService).when(context_).userService(); - Mockito.doReturn(alarmManager).when(applicationContext).getSystemService(android.content.Context.ALARM_SERVICE); - Mockito.doReturn("admin").when(allSharedPreferences).fetchRegisteredANM(); - Mockito.doReturn(true).when(inputManager).hideSoftInputFromWindow(isNull(IBinder.class), anyInt()); - Intent intent = new Intent(RuntimeEnvironment.application, LoginActivityMock.class); - controller = Robolectric.buildActivity(LoginActivityMock.class, intent); - controller.create() - .start() - .visible() - .resume(); - activity = controller.get(); - - Whitebox.setInternalState(activity, "context", context_); - } - - - @Test - public void assertActivityNotNull() { - Assert.assertNotNull(activity); - } - - @Test - public void localLoginTest() { - Mockito.doReturn(true).when(userService).hasARegisteredUser(); - Mockito.doReturn(true).when(userService).isValidLocalLogin(anyString(), any(char[].class)); - Mockito.doReturn(allSharedPreferences).when(context_).allSharedPreferences(); - - EditText username = activity.findViewById(R.id.login_userNameText); - EditText password = activity.findViewById(R.id.login_passwordText); - - username.setText("admin"); - password.setText("password"); - - Button login_button = activity.findViewById(R.id.login_loginButton); - login_button.performClick(); - - Mockito.verify(userService, Mockito.atLeastOnce()).localLogin(anyString(), any(char[].class)); - - } - - @After - public void destroyController() { - try { - activity.finish(); - controller.pause().stop().destroy(); //destroy controller if we can - - } catch (Exception e) { - Log.e(getClass().getCanonicalName(), e.getMessage()); - } - - System.gc(); - } - -} diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityWithRemoteLoginTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityWithRemoteLoginTest.java deleted file mode 100644 index 3dd2d2359..000000000 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/LoginActivityWithRemoteLoginTest.java +++ /dev/null @@ -1,145 +0,0 @@ -package org.smartregister.view.activity; - -import android.app.AlarmManager; -import android.content.Intent; -import android.os.IBinder; -import android.util.Log; -import android.view.inputmethod.InputMethodManager; -import android.widget.Button; -import android.widget.EditText; - -import junit.framework.Assert; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.reflect.Whitebox; -import org.robolectric.Robolectric; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.android.controller.ActivityController; -import org.robolectric.annotation.Config; -import org.smartregister.BaseUnitTest; -import org.smartregister.Context; -import org.smartregister.CoreLibrary; -import org.smartregister.R; -import org.smartregister.customshadows.AndroidTreeViewShadow; -import org.smartregister.customshadows.FontTextViewShadow; -import org.smartregister.domain.LoginResponse; -import org.smartregister.domain.jsonmapping.LoginResponseData; -import org.smartregister.repository.AllSharedPreferences; -import org.smartregister.service.UserService; -import org.smartregister.shadows.AlarmManagerShadow; -import org.smartregister.shadows.MyShadowAsyncTask; -import org.smartregister.shadows.PendingIntentShadow; -import org.smartregister.shadows.ShadowContext; -import org.smartregister.sync.DrishtiSyncScheduler; -import org.smartregister.view.activity.mock.LoginActivityMock; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.when; - -/** - * Created by kaderchowdhury on 11/11/17. - */ -@PowerMockIgnore({"javax.xml.*", "org.xml.sax.*", "org.w3c.dom.*", "org.springframework.context.*", "org.apache.log4j.*"}) -@PrepareForTest({CoreLibrary.class}) -@Config(shadows = {ShadowContext.class, FontTextViewShadow.class, AndroidTreeViewShadow.class, PendingIntentShadow.class, AlarmManagerShadow.class, MyShadowAsyncTask.class}) -public class LoginActivityWithRemoteLoginTest extends BaseUnitTest { - - private ActivityController controller; - - @Mock - private android.content.Context applicationContext; - - @Mock - private AllSharedPreferences allSharedPreferences; - - @Mock - private InputMethodManager inputManager; - - @Mock - private AlarmManager alarmManager; - - @InjectMocks - private LoginActivityMock activity; - - @Mock - private Context context_; - - @Mock - private UserService userService; - - @Before - public void setUp() throws Exception { - org.mockito.MockitoAnnotations.initMocks(this); - CoreLibrary.init(context_); - LoginActivityMock.inputManager = inputManager; -// ShadowSystemClock shadowClock = new ShadowSystemClock(); -// shadowClock.setCurrentTimeMillis(142436987); - - DrishtiSyncScheduler.setReceiverClass(LoginActivityMock.class); - -// Context context = CoreLibrary.getInstance().context().updateApplicationContext(activity.getApplicationContext()); -// this.context_ = context; - when(context_.applicationContext()).thenReturn(applicationContext); - when(context_.updateApplicationContext(any(android.content.Context.class))).thenReturn(context_); - when(context_.userService()).thenReturn(userService); - when(applicationContext.getSystemService(android.content.Context.ALARM_SERVICE)).thenReturn(alarmManager); - when(allSharedPreferences.fetchRegisteredANM()).thenReturn("admin"); - when(inputManager.hideSoftInputFromWindow(isNull(IBinder.class), anyInt())).thenReturn(true); - Intent intent = new Intent(RuntimeEnvironment.application, LoginActivityMock.class); - controller = Robolectric.buildActivity(LoginActivityMock.class, intent); - controller.create() - .start() - .resume() - .visible(); - activity = controller.get(); - - Whitebox.setInternalState(activity, "context", context_); - } - - - @Test - public void assertActivityNotNull() { - Assert.assertNotNull(activity); - } - - private void destroyController() { - try { - activity.finish(); - controller.pause().stop().destroy(); //destroy controller if we can - - } catch (Exception e) { - Log.e(getClass().getCanonicalName(), e.getMessage()); - } - - System.gc(); - } - - @Test - public void remoteLoginTest() { - when(userService.hasARegisteredUser()).thenReturn(false); - when(userService.isValidLocalLogin(anyString(), any(char[].class))).thenReturn(true); - when(userService.isValidRemoteLogin(anyString(),any(char[].class))).thenReturn(LoginResponse.SUCCESS.withPayload(new LoginResponseData())); - when(context_.allSharedPreferences()).thenReturn(allSharedPreferences); - when(allSharedPreferences.fetchBaseURL(anyString())).thenReturn("base url"); - EditText username = activity.findViewById(R.id.login_userNameText); - EditText password = activity.findViewById(R.id.login_passwordText); - username.setText("admin"); - password.setText("password"); - Button login_button = activity.findViewById(R.id.login_loginButton); - login_button.performClick(); - Mockito.verify(userService, Mockito.atLeastOnce()).processLoginResponseDataForUser(anyString(),any(char[].class), any(LoginResponseData.class)); - destroyController(); - - - } - -} diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/mock/LoginActivityMock.java b/opensrp-app/src/test/java/org/smartregister/view/activity/mock/LoginActivityMock.java deleted file mode 100644 index dd990871c..000000000 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/mock/LoginActivityMock.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.smartregister.view.activity.mock; - -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.view.View; -import android.view.inputmethod.InputMethodManager; - -import org.smartregister.Context; -import org.smartregister.R; -import org.smartregister.view.activity.LoginActivity; - -/** - * Created by Raihan Ahmed on 11/11/17. - */ - -public class LoginActivityMock extends LoginActivity { - - static Context mockactivitycontext; - - public static InputMethodManager inputManager; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - setTheme(R.style.AppTheme); //we need this here - super.onCreate(savedInstanceState); - } - - @Override - public Object getSystemService(String name) { - if (name.equalsIgnoreCase(INPUT_METHOD_SERVICE)) { - return inputManager; - } else { - return super.getSystemService(name); - } - } - - @Nullable - @Override - public View getCurrentFocus() { - return findViewById(R.id.login_userNameText); - } -} diff --git a/opensrp-app/src/test/java/org/smartregister/view/fragment/SecuredFragmentTest.java b/opensrp-app/src/test/java/org/smartregister/view/fragment/SecuredFragmentTest.java index c82c20e09..94b1bf7c0 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/fragment/SecuredFragmentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/fragment/SecuredFragmentTest.java @@ -177,23 +177,6 @@ public void assertLogoutUserInvokesUserserviceLogoutMethod() { } - @Test - public void assertLogoutUserNavigatesToLoginPage() { - - Mockito.doNothing().when(securedFragment).startActivity(ArgumentMatchers.any(Intent.class)); - Mockito.doNothing().when(userService).logout(); - - securedFragment.logoutUser(); - - Mockito.verify(securedFragment).startActivity(intentArgumentCaptor.capture()); - Intent navigationIntent = intentArgumentCaptor.getValue(); - - Assert.assertNotNull(navigationIntent); - - Assert.assertEquals("org.smartregister.view.activity.LoginActivity", navigationIntent.getComponent().getClassName()); - - } - @Test public void assertStartFormActivityInvokesNavigationToFormActivity() { From adf7b83575b714003bebdeb176285230d4bfb1fe Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Fri, 17 Jul 2020 12:31:05 +0300 Subject: [PATCH 51/70] Code clean up - Clean up Authentication logic classes - Fix failing tests --- .../smartregister/account/AccountAuthenticator.java | 9 +++++---- .../smartregister/login/task/RemoteLoginTask.java | 12 +++++++----- .../service/ImageUploadSyncService.java | 3 ++- .../sync/helper/SyncSettingsServiceHelper.java | 9 +++++++-- .../sync/helper/SyncSettingsServiceHelperTest.java | 6 +++++- .../view/fragment/BaseRegisterFragmentTest.java | 2 -- 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java index 92d3a9389..2b9887f16 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java @@ -50,17 +50,18 @@ public Bundle getAuthToken(AccountAuthenticatorResponse response, Account accoun AccountManager accountManager = CoreLibrary.getInstance().getAccountManager(); String authToken = accountManager.peekAuthToken(account, authTokenType); - String refreshToken = ""; + String refreshToken; Timber.d("peekAuthToken " + authToken); if (TextUtils.isEmpty(authToken)) { - final String password = accountManager.getPassword(account); - if (password != null) { + refreshToken = accountManager.getPassword(account); + + if (refreshToken != null) { try { Timber.d("Authenticate with saved credentials"); - AccountResponse accountResponse = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticateRefreshToken(password); + AccountResponse accountResponse = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticateRefreshToken(refreshToken); if (accountResponse.getStatus() == HttpStatus.SC_OK) { authToken = accountResponse.getAccessToken(); refreshToken = accountResponse.getRefreshToken(); diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index 005f7f817..d68a56ebe 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -63,12 +63,12 @@ protected LoginResponse doInBackground(Void... params) { LoginResponse loginResponse; try { - AccountConfiguration accountConfiguration = CoreLibrary.getInstance().context().getHttpAgent().fetchOAuthConfiguration(); + AccountConfiguration accountConfiguration = getOpenSRPContext().getHttpAgent().fetchOAuthConfiguration(); boolean isKeycloakConfigured = accountConfiguration != null; //Persist config resources - SharedPreferences.Editor sharedPrefEditor = CoreLibrary.getInstance().context().allSharedPreferences().getPreferences().edit(); + SharedPreferences.Editor sharedPrefEditor = getOpenSRPContext().allSharedPreferences().getPreferences().edit(); sharedPrefEditor.putBoolean(AccountHelper.CONFIGURATION_CONSTANTS.IS_KEYCLOAK_CONFIGURED, isKeycloakConfigured); sharedPrefEditor.apply(); @@ -76,7 +76,7 @@ protected LoginResponse doInBackground(Void... params) { if (!isKeycloakConfigured) { accountConfiguration = new AccountConfiguration(); accountConfiguration.setGrantTypesSupported(Arrays.asList(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD)); - accountConfiguration.setTokenEndpoint(CoreLibrary.getInstance().context().configuration().dristhiBaseURL() + AccountHelper.OAUTH.TOKEN_ENDPOINT); + accountConfiguration.setTokenEndpoint(getOpenSRPContext().configuration().dristhiBaseURL() + AccountHelper.OAUTH.TOKEN_ENDPOINT); accountConfiguration.setAuthorizationEndpoint(""); accountConfiguration.setIssuerEndpoint(""); } @@ -92,7 +92,7 @@ protected LoginResponse doInBackground(Void... params) { sharedPrefEditor.putString(AccountHelper.CONFIGURATION_CONSTANTS.USERINFO_ENDPOINT_URL, accountConfiguration.getUserinfoEndpoint()); sharedPrefEditor.apply(); - AccountResponse response = CoreLibrary.getInstance().context().getHttpAgent().oauth2authenticate(mUsername, mPassword, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, accountConfiguration.getTokenEndpoint()); + AccountResponse response = getOpenSRPContext().getHttpAgent().oauth2authenticate(mUsername, mPassword, AccountHelper.OAUTH.GRANT_TYPE.PASSWORD, accountConfiguration.getTokenEndpoint()); AccountManager mAccountManager = CoreLibrary.getInstance().getAccountManager(); @@ -142,7 +142,9 @@ protected LoginResponse doInBackground(Void... params) { } catch (Exception e) { - loginResponse = CUSTOM_SERVER_RESPONSE.withMessage(e.getMessage()); + loginResponse = CUSTOM_SERVER_RESPONSE.withMessage(getOpenSRPContext().applicationContext().getResources().getString(R.string.unknown_response)); + + Timber.e(e); } return loginResponse; diff --git a/opensrp-app/src/main/java/org/smartregister/service/ImageUploadSyncService.java b/opensrp-app/src/main/java/org/smartregister/service/ImageUploadSyncService.java index 691146844..95b7b8a18 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/ImageUploadSyncService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/ImageUploadSyncService.java @@ -43,7 +43,8 @@ protected void onHandleIntent(Intent intent) { if (response.contains("success")) { imageRepo.close(profileImages.get(i).getImageid()); } else { - Timber.e(new StringBuilder("Image Upload: could NOT upload image ID: ").append(profileImages.get(i).getImageid()).append(" PATH: ").append(profileImages.get(i).getFilepath()).toString()); + Timber.e("Image Upload: could NOT upload image ID: %s %s %s ", profileImages.get(i).getImageid(), " PATH: ", profileImages.get(i).getFilepath()); + } } } catch (Exception e) { diff --git a/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java b/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java index e802bcb75..d5554b7a8 100644 --- a/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java @@ -70,8 +70,7 @@ public int processIntent() throws JSONException { private JSONArray getSettings() throws JSONException { - AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); - String authToken = AccountHelper.getCachedOAuthToken(sharedPreferences.fetchRegisteredANM(), authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + String authToken = getAccessToken(); JSONArray settings = pullSettingsFromServer(getInstance().getSyncConfiguration().getSettingsSyncFilterValue(), authToken); getGlobalSettings(settings, authToken); @@ -79,6 +78,12 @@ private JSONArray getSettings() throws JSONException { return settings; } + @VisibleForTesting + protected String getAccessToken() { + AccountAuthenticatorXml authenticatorXml = CoreLibrary.getInstance().getAccountAuthenticatorXml(); + return AccountHelper.getCachedOAuthToken(sharedPreferences.fetchRegisteredANM(), authenticatorXml.getAccountType(), AccountHelper.TOKEN_TYPE.PROVIDER); + } + @VisibleForTesting protected CoreLibrary getInstance() { return CoreLibrary.getInstance(); diff --git a/opensrp-app/src/test/java/org/smartregister/sync/helper/SyncSettingsServiceHelperTest.java b/opensrp-app/src/test/java/org/smartregister/sync/helper/SyncSettingsServiceHelperTest.java index 97b3d3959..5009a3d45 100644 --- a/opensrp-app/src/test/java/org/smartregister/sync/helper/SyncSettingsServiceHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/sync/helper/SyncSettingsServiceHelperTest.java @@ -48,6 +48,7 @@ public class SyncSettingsServiceHelperTest extends BaseRobolectricUnitTest { @Mock private SQLiteDatabase sqLiteDatabase; private String settingsResponse = "[{\"identifier\":\"site_characteristics\",\"settings\":[{\"settingMetadataId\":\"4\",\"serverVersion\":1594125118616,\"description\":\"Is the HIV prevalence consistently &gt; 1% in pregnant women attending antenatal clinics at your facility?\",\"label\":\"Generalized HIV epidemic\",\"type\":\"Setting\",\"value\":\"true\",\"uuid\":\"e42f3e1f-e8b9-4694-8efa-f021e66b5691\",\"key\":\"site_anc_hiv\",\"settingIdentifier\":\"site_characteristics\"},{\"settingMetadataId\":\"1\",\"serverVersion\":1594125118616,\"description\":\"\\\"Are all of the following in place at your facility: \\r\\n1. A protocol or standard operating procedure for Intimate Partner Violence (IPV); \\r\\n2. A health worker trained on how to ask about IPV and how to provide the minimum response or beyond;\\r\\n3. A private setting; \\r\\n4. A way to ensure confidentiality; \\r\\n5. Time to allow for appropriate disclosure; and\\r\\n6. A system for referral in place. \\\"\",\"label\":\"Minimum requirements for IPV assessment\",\"type\":\"Setting\",\"value\":\"true\",\"uuid\":\"fb2ca30f-3de5-4bfc-a2d2-987e9c383cd7\",\"key\":\"site_ipv_assess\",\"settingIdentifier\":\"site_characteristics\"}],\"serverVersion\":1593791975015,\"providerId\":\"demo\",\"locationId\":\"44de66fb-e6c6-4bae-92bb-386dfe626eba\",\"teamId\":\"6c8d2b9b-2246-47c2-949b-4fe29e888cc8\",\"_rev\":\"v1\",\"team\":\"Bukesa\",\"_id\":\"9918b87c-a71f-462d-b1c9-33a2d50e4c15\",\"type\":\"Setting\"}]"; + private static final String SAMPLE_TEST_TOKEN = "Sample_TOKEN"; @Before public void setUp() { @@ -81,10 +82,13 @@ public void testProcessIntent() throws JSONException { List params = new ArrayList<>(); params.add("locationId=location-uuid"); - Mockito.doReturn(params).when(syncConfiguration).getExtraSettingsParameters(); + Mockito.doReturn(params).when(syncConfiguration).getExtraSettingsParameters(); Mockito.doReturn(new Response<>(ResponseStatus.success, settingsResponse)).when(syncSettingsServiceHelper).getResponse(ArgumentMatchers.anyString(), ArgumentMatchers.anyString()); + Mockito.doReturn(SAMPLE_TEST_TOKEN).when(syncSettingsServiceHelper).getAccessToken(); + int size = syncSettingsServiceHelper.processIntent(); + Assert.assertEquals(3, size); } } diff --git a/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java b/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java index d4daea9bc..4be8cfb77 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/fragment/BaseRegisterFragmentTest.java @@ -18,7 +18,6 @@ import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; @@ -270,7 +269,6 @@ public void assertOnQRCodeSucessfullyScannedInvokesFilterWithCorrectParams() { } @Test - @Ignore public void assertOnQRCodeSucessfullyScannedInvokessetUniqueIDWithCorrectParams() { String OPENSRP_ID = "8232-372-8L"; From ef043839aa298e481a3430cd6b31ece8ff81edab Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Sat, 18 Jul 2020 17:21:11 +0300 Subject: [PATCH 52/70] Fix local authentication bug - Fix issue local login not working for team and location auth config types - Refactor to use bytes for all password refs --- opensrp-app/build.gradle | 1 + .../smartregister/util/FakeUserService.java | 2 +- .../account/AccountAuthenticator.java | 5 + .../smartregister/account/AccountHelper.java | 4 +- .../login/interactor/BaseLoginInteractor.java | 121 +++++++------- .../login/task/RemoteLoginTask.java | 10 +- .../smartregister/repository/Repository.java | 12 +- .../helper/OpenSRPDatabaseErrorHandler.java | 35 ++++ .../security/SecurityHelper.java | 18 +-- .../smartregister/service/UserService.java | 150 +++++++++--------- .../java/org/smartregister/util/Session.java | 1 + .../view/activity/DrishtiApplication.java | 8 +- .../service/UserServiceTest.java | 2 +- .../view/activity/DrishtiApplicationTest.java | 2 +- 14 files changed, 194 insertions(+), 177 deletions(-) create mode 100644 opensrp-app/src/main/java/org/smartregister/repository/helper/OpenSRPDatabaseErrorHandler.java diff --git a/opensrp-app/build.gradle b/opensrp-app/build.gradle index 82eb74095..0709af9eb 100644 --- a/opensrp-app/build.gradle +++ b/opensrp-app/build.gradle @@ -93,6 +93,7 @@ android { versionName project.VERSION_NAME testInstrumentationRunner "android.test.InstrumentationTestRunner" buildConfigField "long", "BUILD_TIMESTAMP", System.currentTimeMillis() + "L" + buildConfigField "int", "ENCRYPTION_VERSION", '1' } sourceSets { diff --git a/opensrp-app/src/androidTest/java/org/smartregister/util/FakeUserService.java b/opensrp-app/src/androidTest/java/org/smartregister/util/FakeUserService.java index 7a229ea35..49bb3dc1a 100644 --- a/opensrp-app/src/androidTest/java/org/smartregister/util/FakeUserService.java +++ b/opensrp-app/src/androidTest/java/org/smartregister/util/FakeUserService.java @@ -22,7 +22,7 @@ public FakeUserService() { } @Override - public boolean isValidLocalLogin(String userName, char[] password) { + public boolean isValidLocalLogin(String userName, byte[] password) { assertExpectedCredentials(userName, password); actualCalls.add("local"); return shouldSucceedLocalLogin; diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java index 2b9887f16..0a3dcbb98 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticator.java @@ -7,6 +7,7 @@ import android.accounts.NetworkErrorException; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.text.TextUtils; @@ -68,6 +69,10 @@ public Bundle getAuthToken(AccountAuthenticatorResponse response, Account accoun accountManager.setPassword(account, refreshToken); accountManager.setAuthToken(account, authTokenType, authToken); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + accountManager.notifyAccountAuthenticated(account); + } } } catch (Exception e) { diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java index 71ec29ea6..853191dcf 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java @@ -46,8 +46,8 @@ public static final class INTENT_KEY { public final static String AUTH_TYPE = "AUTH_TYPE"; public final static String ACCOUNT_NAME = "ACCOUNT_NAME"; public final static String IS_NEW_ACCOUNT = "IS_NEW_ACCOUNT"; - public final static String ACCOUNT_GROUP_ID = "ACCOUNT_GROUP_ID"; - public final static String ACCOUNT_PASSWORD = "ACCOUNT_PASSWORD"; + public final static String ACCOUNT_SECRET_KEY = "ACCOUNT_SECRET_KEY"; + public final static String ACCOUNT_REFRESH_TOKEN = "ACCOUNT_REFRESH_TOKEN"; public final static String ACCOUNT_PASSWORD_SALT = "ACCOUNT_PASSWORD_SALT"; } diff --git a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java index cc4c8ac7b..682eb99e9 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java +++ b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java @@ -19,7 +19,6 @@ import org.smartregister.job.P2pServiceJob; import org.smartregister.job.PullUniqueIdsServiceJob; import org.smartregister.job.SyncSettingsServiceJob; -import org.smartregister.listener.OnCompleteClearDataCallback; import org.smartregister.login.task.LocalLoginTask; import org.smartregister.login.task.RemoteLoginTask; import org.smartregister.multitenant.ResetAppHelper; @@ -93,7 +92,7 @@ private void localLogin(WeakReference view, String userN } else if (isAuthenticated && (!AllConstants.TIME_CHECK || TimeStatus.OK.equals(getUserService().validateStoredServerTimeZone()))) { - navigateToHomePage(userName, password); + navigateToHomePage(userName); } else { loginWithLocalFlag(view, false, userName, password); @@ -104,24 +103,21 @@ private void localLogin(WeakReference view, String userN } - private void navigateToHomePage(String userName, char[] password) { + private void navigateToHomePage(String userName) { - getUserService().localLogin(userName, password); + getUserService().localLoginWith(userName); getLoginView().goToHome(false); CoreLibrary.getInstance().initP2pLibrary(userName); - new Thread(new Runnable() { - @Override - public void run() { - Timber.i("Starting DrishtiSyncScheduler " + DateTime.now().toString()); + new Thread(() -> { + Timber.i("Starting DrishtiSyncScheduler " + DateTime.now().toString()); - scheduleJobsImmediately(); + scheduleJobsImmediately(); - Timber.i("Started DrishtiSyncScheduler " + DateTime.now().toString()); + Timber.i("Started DrishtiSyncScheduler " + DateTime.now().toString()); - CoreLibrary.getInstance().context().getUniqueIdRepository().releaseReservedIds(); - } + CoreLibrary.getInstance().context().getUniqueIdRepository().releaseReservedIds(); }).start(); } @@ -132,68 +128,59 @@ private void remoteLogin(final String userName, final char[] password, final Acc getSharedPreferences().savePreference("DRISHTI_BASE_URL", getApplicationContext().getString(R.string.opensrp_url)); } if (!getSharedPreferences().fetchBaseURL("").isEmpty()) { - tryRemoteLogin(userName, password, accountAuthenticatorXml, new Listener() { - - public void onEvent(LoginResponse loginResponse) { - getLoginView().enableLoginButton(true); - if (loginResponse == LoginResponse.SUCCESS) { - String username = loginResponse.payload() != null && loginResponse.payload().user != null && StringUtils.isNotBlank(loginResponse.payload().user.getUsername()) - ? loginResponse.payload().user.getUsername() : userName; - if (getUserService().isUserInPioneerGroup(username)) { - TimeStatus timeStatus = getUserService().validateDeviceTime( - loginResponse.payload(), AllConstants.MAX_SERVER_TIME_DIFFERENCE - ); - if (!AllConstants.TIME_CHECK || timeStatus.equals(TimeStatus.OK)) { - - remoteLoginWith(username, password, loginResponse); - - } else { - if (timeStatus.equals(TimeStatus.TIMEZONE_MISMATCH)) { - TimeZone serverTimeZone = UserService.getServerTimeZone(loginResponse.payload()); + tryRemoteLogin(userName, password, accountAuthenticatorXml, loginResponse -> { + getLoginView().enableLoginButton(true); + if (loginResponse == LoginResponse.SUCCESS) { + String username = loginResponse.payload() != null && loginResponse.payload().user != null && StringUtils.isNotBlank(loginResponse.payload().user.getUsername()) + ? loginResponse.payload().user.getUsername() : userName; + if (getUserService().isUserInPioneerGroup(username)) { + TimeStatus timeStatus = getUserService().validateDeviceTime( + loginResponse.payload(), AllConstants.MAX_SERVER_TIME_DIFFERENCE + ); + if (!AllConstants.TIME_CHECK || timeStatus.equals(TimeStatus.OK)) { + + remoteLoginWith(username, loginResponse); - getLoginView().showErrorDialog(getApplicationContext().getString(timeStatus.getMessage(), - serverTimeZone.getDisplayName())); - } else { - getLoginView().showErrorDialog(getApplicationContext().getString(timeStatus.getMessage())); - } - } } else { + if (timeStatus.equals(TimeStatus.TIMEZONE_MISMATCH)) { + TimeZone serverTimeZone = UserService.getServerTimeZone(loginResponse.payload()); - if (CoreLibrary.getInstance().getSyncConfiguration().clearDataOnNewTeamLogin()) { - getLoginView().showClearDataDialog(new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - if (which == DialogInterface.BUTTON_POSITIVE) { - ResetAppHelper resetAppHelper = new ResetAppHelper(DrishtiApplication.getInstance()); - resetAppHelper.startResetProcess(getLoginView().getAppCompatActivity(), new OnCompleteClearDataCallback() { - @Override - public void onComplete() { - login(new WeakReference<>(getLoginView()), userName, password); - } - }); - } - } - }); + getLoginView().showErrorDialog(getApplicationContext().getString(timeStatus.getMessage(), + serverTimeZone.getDisplayName())); } else { - // Valid user from wrong group trying to log in - getLoginView().showErrorDialog(getApplicationContext().getString(R.string.unauthorized_group)); + getLoginView().showErrorDialog(getApplicationContext().getString(timeStatus.getMessage())); } + } + } else { + if (CoreLibrary.getInstance().getSyncConfiguration().clearDataOnNewTeamLogin()) { + getLoginView().showClearDataDialog((dialog, which) -> { + + dialog.dismiss(); + + if (which == DialogInterface.BUTTON_POSITIVE) { + ResetAppHelper resetAppHelper = new ResetAppHelper(DrishtiApplication.getInstance()); + resetAppHelper.startResetProcess(getLoginView().getAppCompatActivity(), () -> login(new WeakReference<>(getLoginView()), userName, password)); + } + }); + } else { + // Valid user from wrong group trying to log in + getLoginView().showErrorDialog(getApplicationContext().getString(R.string.unauthorized_group)); } + + } + } else { + if (loginResponse == null) { + getLoginView().showErrorDialog("Sorry, your loginWithLocalFlag failed. Please try again"); } else { - if (loginResponse == null) { - getLoginView().showErrorDialog("Sorry, your loginWithLocalFlag failed. Please try again"); + if (loginResponse == NO_INTERNET_CONNECTIVITY) { + getLoginView().showErrorDialog(getApplicationContext().getResources().getString(R.string.no_internet_connectivity)); + } else if (loginResponse == UNKNOWN_RESPONSE) { + getLoginView().showErrorDialog(getApplicationContext().getResources().getString(R.string.unknown_response)); + } else if (loginResponse == UNAUTHORIZED) { + getLoginView().showErrorDialog(getApplicationContext().getResources().getString(R.string.unauthorized)); } else { - if (loginResponse == NO_INTERNET_CONNECTIVITY) { - getLoginView().showErrorDialog(getApplicationContext().getResources().getString(R.string.no_internet_connectivity)); - } else if (loginResponse == UNKNOWN_RESPONSE) { - getLoginView().showErrorDialog(getApplicationContext().getResources().getString(R.string.unknown_response)); - } else if (loginResponse == UNAUTHORIZED) { - getLoginView().showErrorDialog(getApplicationContext().getResources().getString(R.string.unauthorized)); - } else { - getLoginView().showErrorDialog(loginResponse.message()); - } + getLoginView().showErrorDialog(loginResponse.message()); } } } @@ -216,8 +203,8 @@ private void tryRemoteLogin(final String userName, final char[] password, final remoteLoginTask.execute(); } - private void remoteLoginWith(String userName, char[] password, LoginResponse loginResponse) { - getUserService().processLoginResponseDataForUser(userName, password, loginResponse.payload()); + private void remoteLoginWith(String userName, LoginResponse loginResponse) { + getUserService().processLoginResponseDataForUser(userName, loginResponse.payload()); processServerSettings(loginResponse); scheduleJobsPeriodically(); diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index d68a56ebe..261ebafc6 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -102,21 +102,21 @@ protected LoginResponse doInBackground(Void... params) { if (loginResponse != null && loginResponse.equals(LoginResponse.SUCCESS)) { - Bundle userData = getOpenSRPContext().userService().saveUserGroup(mUsername, mPassword, loginResponse.payload()); + Bundle userData = getOpenSRPContext().userService().saveUserCredentials(mUsername, mPassword, loginResponse.payload()); mAccountManager.addAccountExplicitly(account, response.getRefreshToken(), userData); mAccountManager.setAuthToken(account, mLoginView.getAuthTokenType(), response.getAccessToken()); mAccountManager.setPassword(account, response.getRefreshToken()); - mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID)); - mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_NAME, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_NAME)); - mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD)); + mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_SECRET_KEY, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_SECRET_KEY)); mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT)); + mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_NAME, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_NAME)); + mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_REFRESH_TOKEN, response.getRefreshToken()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mAccountManager.notifyAccountAuthenticated(account); } - if (getOpenSRPContext().userService().getGroupId(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { + if (getOpenSRPContext().userService().getAccountSecretKey(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { publishProgress(R.string.loading_client_settings); diff --git a/opensrp-app/src/main/java/org/smartregister/repository/Repository.java b/opensrp-app/src/main/java/org/smartregister/repository/Repository.java index 81c4d32b4..000e0d177 100755 --- a/opensrp-app/src/main/java/org/smartregister/repository/Repository.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/Repository.java @@ -2,6 +2,7 @@ import android.content.Context; +import net.sqlcipher.DefaultDatabaseErrorHandler; import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteDatabaseHook; import net.sqlcipher.database.SQLiteException; @@ -12,6 +13,7 @@ import org.smartregister.CoreLibrary; import org.smartregister.commonregistry.CommonFtsObject; import org.smartregister.exception.DatabaseMigrationException; +import org.smartregister.repository.helper.OpenSRPDatabaseErrorHandler; import org.smartregister.util.DatabaseMigrationUtils; import org.smartregister.util.Session; import org.smartregister.view.activity.DrishtiApplication; @@ -156,15 +158,17 @@ public SQLiteDatabase getWritableDatabase() { return getWritableDatabase(password()); } - private boolean isDatabaseWritable(char[] password) { + private boolean isDatabaseWritable(byte[] password) { + + SQLiteDatabase database = SQLiteDatabase .openDatabase(databasePath.getPath(), password, null, - SQLiteDatabase.OPEN_READONLY, hook); + SQLiteDatabase.OPEN_READONLY, hook, new OpenSRPDatabaseErrorHandler()); database.close(); return true; } - public boolean canUseThisPassword(char[] password) { + public boolean canUseThisPassword(byte[] password) { try { return isDatabaseWritable(password); } catch (SQLiteException e) { @@ -190,7 +194,7 @@ public boolean canUseThisPassword(char[] password) { } } - private char[] password() { + private byte[] password() { return DrishtiApplication.getInstance().getPassword(); } diff --git a/opensrp-app/src/main/java/org/smartregister/repository/helper/OpenSRPDatabaseErrorHandler.java b/opensrp-app/src/main/java/org/smartregister/repository/helper/OpenSRPDatabaseErrorHandler.java new file mode 100644 index 000000000..69f69deef --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/repository/helper/OpenSRPDatabaseErrorHandler.java @@ -0,0 +1,35 @@ +package org.smartregister.repository.helper; + +import android.util.Log; + +import net.sqlcipher.DatabaseErrorHandler; +import net.sqlcipher.database.SQLiteDatabase; + +/** + * Created by ndegwamartin on 18/07/2020. + */ +public class OpenSRPDatabaseErrorHandler implements DatabaseErrorHandler { + + private final String TAG = getClass().getSimpleName(); + + /** + * defines the default method to be invoked when database corruption is detected. + * + * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption + * is detected. + */ + public void onCorruption(SQLiteDatabase dbObj) { + Log.e(TAG, "Corruption reported by sqlite on database, db file path: " + dbObj.getPath()); + + if (dbObj.isOpen()) { + Log.e(TAG, "Database object for corrupted database is already open, closing"); + + try { + dbObj.close(); + } catch (Exception e) { + Log.e(TAG, "Exception closing Database object for corrupted database, ignored", e); + } + } + + } +} diff --git a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java index 8ba95e78b..f49b6bee9 100644 --- a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java @@ -4,6 +4,8 @@ import android.text.Editable; import android.util.Base64; +import net.sqlcipher.database.SQLiteDatabase; + import org.apache.commons.codec.CharEncoding; import org.apache.commons.lang3.StringUtils; @@ -100,15 +102,8 @@ private static void clearStringBuffer(StringBuffer stringBuffer) { * @return an array of bytes, a conversion from the chars array */ public static byte[] toBytes(char[] chars) { - CharBuffer charBuffer = CharBuffer.wrap(chars); - - ByteBuffer byteBuffer = CHARSET.encode(charBuffer); - byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()); - - clearArray(byteBuffer.array()); - - return bytes; + return SQLiteDatabase.getBytes(chars); } @@ -120,12 +115,7 @@ public static byte[] toBytes(char[] chars) { */ public static char[] toChars(byte[] bytes) { - char[] convertedChar = new char[bytes.length]; - for (int i = 0; i < bytes.length; i++) { - convertedChar[i] = (char) bytes[i]; - } - - return convertedChar; + return SQLiteDatabase.getChars(bytes); } /** diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 2dfcf4edb..11b6c6f4c 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -209,7 +209,7 @@ public TimeStatus validateDeviceTime(LoginResponseData userInfo, long serverTime return TimeStatus.ERROR; } - public boolean isValidLocalLogin(String userName, char[] password) { + public boolean isValidLocalLogin(String userName, byte[] password) { return allSharedPreferences.fetchRegisteredANM().equals(userName) && DrishtiApplication.getInstance().getRepository() .canUseThisPassword(password) && !allSharedPreferences.fetchForceRemoteLogin(userName); } @@ -217,26 +217,23 @@ public boolean isValidLocalLogin(String userName, char[] password) { public boolean isUserInValidGroup(final String userName, final char[] password) { // Check if everything OK for local login if (keyStore != null && userName != null && password != null && !allSharedPreferences.fetchForceRemoteLogin(userName)) { + String username = userName.equalsIgnoreCase(allSharedPreferences.fetchRegisteredANM()) ? allSharedPreferences.fetchRegisteredANM() : userName; + byte[] storedHash = null; byte[] passwordHash = null; byte[] passwordSalt = null; try { - KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(username); - if (privateKeyEntry != null) { - // Compare stored password hash with provided password hash - storedHash = SecurityHelper.nullSafeBase64Decode(AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); + // Compare stored password hash with provided password hash + storedHash = getAccountSecretKey(username); - passwordSalt = SecurityHelper.nullSafeBase64Decode(AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); - passwordHash = SecurityHelper.hashPassword(password, passwordSalt); + passwordSalt = SecurityHelper.nullSafeBase64Decode(AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); + passwordHash = SecurityHelper.hashPassword(getEncryptionParamValue(username, password), passwordSalt); - if (storedHash != null && Arrays.equals(storedHash, passwordHash)) { - char[] groupId = getGroupId(username, privateKeyEntry); - if (groupId != null) { - return isValidGroupId(groupId); - } - } + if (storedHash != null && Arrays.equals(storedHash, passwordHash)) { + + return isValidDBPassword(storedHash); } } catch (Exception e) { Timber.e(e); @@ -251,15 +248,28 @@ public boolean isUserInValidGroup(final String userName, final char[] password) return false; } - private boolean isValidGroupId(char[] groupId) { - return DrishtiApplication.getInstance().getRepository().canUseThisPassword(groupId); + private char[] getEncryptionParamValue(String username, char[] password) { + + char[] encryptionParamValue = password; + SyncFilter syncFilter = CoreLibrary.getInstance().getSyncConfiguration().getEncryptionParam(); + + if (SyncFilter.TEAM.equals(syncFilter) || SyncFilter.TEAM_ID.equals(syncFilter)) { + encryptionParamValue = allSharedPreferences.fetchDefaultTeamId(username).toCharArray(); + } else if (SyncFilter.LOCATION.equals(syncFilter) || SyncFilter.LOCATION_ID.equals(syncFilter)) { + encryptionParamValue = allSharedPreferences.fetchDefaultLocalityId(username).toCharArray(); + } + return encryptionParamValue; + } + + private boolean isValidDBPassword(byte[] password) { + return DrishtiApplication.getInstance().getRepository().canUseThisPassword(password); } - public char[] getGroupId(String userName) { + public byte[] getAccountSecretKey(String userName) { if (keyStore != null && userName != null) { try { KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); - return getGroupId(userName, privateKeyEntry); + return getAccountSecretKey(userName, privateKeyEntry); } catch (Exception e) { Timber.e(e); } @@ -267,14 +277,14 @@ public char[] getGroupId(String userName) { return null; } - public char[] getGroupId(String userName, KeyStore.PrivateKeyEntry privateKeyEntry) { + private byte[] getAccountSecretKey(String userName, KeyStore.PrivateKeyEntry privateKeyEntry) { if (privateKeyEntry != null) { - String encryptedGroupId = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); + String encryptedSecretKey = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_SECRET_KEY, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); - if (encryptedGroupId != null) { + if (encryptedSecretKey != null) { try { - return SecurityHelper.toChars(decryptString(privateKeyEntry, encryptedGroupId)); + return decryptString(privateKeyEntry, encryptedSecretKey); } catch (Exception e) { Timber.e(e); } @@ -295,11 +305,11 @@ public boolean isUserInPioneerGroup(String userName) { if (userName.equals(pioneerUser)) { return true; } else { - char[] userGroupId = getGroupId(userName); - char[] pioneerGroupId = getGroupId(pioneerUser); + byte[] currentUserSecretKey = getAccountSecretKey(userName); + byte[] pioneerUserSecretKey = getAccountSecretKey(pioneerUser); - if (userGroupId != null && Arrays.equals(pioneerGroupId, userGroupId)) { - return isValidGroupId(userGroupId); + if (currentUserSecretKey != null && Arrays.equals(pioneerUserSecretKey, currentUserSecretKey)) { + return isValidDBPassword(currentUserSecretKey); } } @@ -324,7 +334,7 @@ public LoginResponse isValidRemoteLogin(String userName, char[] password) { LoginResponse loginResponse = httpAgent.urlCanBeAccessWithGivenCredentials(requestURL, userName, password); if (loginResponse != null && loginResponse.equals(LoginResponse.SUCCESS)) { - saveUserGroup(userName, password, loginResponse.payload()); + saveUserCredentials(userName, password, loginResponse.payload()); } return loginResponse; @@ -339,54 +349,32 @@ public Response getLocationInformation() { return httpAgent.fetch(requestURL); } - private boolean loginWith(String userName, char[] password) { + public boolean localLoginWith(String userName) { boolean loginSuccessful = true; - if (usesGroupIdAsDBPassword(userName)) { + try { - String encryptedGroupId = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); + byte[] secretKey = getAccountSecretKey(userName); + setupContextForLogin(secretKey); - try { - KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); - if (privateKeyEntry != null) { - byte[] groupId = decryptString(privateKeyEntry, encryptedGroupId); - setupContextForLogin(SecurityHelper.toChars(groupId)); - SecurityHelper.clearArray(groupId); - } - } catch (Exception e) { - Timber.e(e); - loginSuccessful = false; + if (!allSharedPreferences.fetchRegisteredANM().equalsIgnoreCase(userName)) { + allSharedPreferences.updateANMUserName(userName); } - } else { - setupContextForLogin(password); - } - String username = userName; - if (allSharedPreferences.fetchRegisteredANM().equalsIgnoreCase(userName)) - username = allSharedPreferences.fetchRegisteredANM(); - allSharedPreferences.updateANMUserName(username); - DrishtiApplication.getInstance().getRepository().getReadableDatabase(); - allSettings.registerANM(username); - return loginSuccessful; - } - /** - * Checks whether to use the groupId for the current user to decrypt the database - * - * @param username the username - * @return TRUE if the user decrypts the database using the groupId - */ - private boolean usesGroupIdAsDBPassword(String username) { - return AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, username, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()) != null; - } + DrishtiApplication.getInstance().getRepository().getReadableDatabase(); - public void localLogin(String userName, char[] password) { - loginWith(userName, password); + } catch (Exception e) { + Timber.e(e); + loginSuccessful = false; + } + + return loginSuccessful; } - public void processLoginResponseDataForUser(String userName, char[] password, LoginResponseData userInfo) { + public void processLoginResponseDataForUser(String userName, LoginResponseData userInfo) { String username = userInfo.user != null && StringUtils.isNotBlank(userInfo.user.getUsername()) ? userInfo.user.getUsername() : userName; - boolean loginSuccessful = loginWith(username, password); + boolean loginSuccessful = localLoginWith(username); saveAnmLocation(getUserLocation(userInfo)); saveAnmTeam(getUserTeam(userInfo)); saveUserInfo(getUserData(userInfo)); @@ -564,7 +552,7 @@ public void saveUserInfo(User user) { * @param userInfo The user's info from the * endpoint (should contain the {team}.{team}.{uuid} key) */ - public Bundle saveUserGroup(String userName, char[] password, LoginResponseData userInfo) { + public Bundle saveUserCredentials(String userName, char[] password, LoginResponseData userInfo) { Bundle bundle = new Bundle(); String username = userInfo.user != null && StringUtils.isNotBlank(userInfo.user.getUsername()) ? userInfo.user.getUsername() : userName; @@ -572,7 +560,7 @@ public Bundle saveUserGroup(String userName, char[] password, LoginResponseData if (keyStore != null && username != null) { - byte[] groupId = null; + char[] encryptionParamValue = null; try { @@ -582,30 +570,29 @@ public Bundle saveUserGroup(String userName, char[] password, LoginResponseData return null; } - PasswordHash passwordHash = SecurityHelper.getPasswordHash(password); SyncConfiguration syncConfiguration = CoreLibrary.getInstance().getSyncConfiguration(); if (syncConfiguration.getEncryptionParam() != null) { SyncFilter syncFilter = syncConfiguration.getEncryptionParam(); if (SyncFilter.TEAM.equals(syncFilter) || SyncFilter.TEAM_ID.equals(syncFilter)) { - groupId = SecurityHelper.toBytes(getUserDefaultTeamId(userInfo).toCharArray()); + encryptionParamValue = getUserDefaultTeamId(userInfo).toCharArray(); } else if (SyncFilter.LOCATION.equals(syncFilter) || SyncFilter.LOCATION_ID.equals(syncFilter)) { - groupId = SecurityHelper.toBytes(getUserLocationId(userInfo).toCharArray()); + encryptionParamValue = getUserLocationId(userInfo).toCharArray(); } else if (SyncFilter.PROVIDER.equals(syncFilter)) { - groupId = SecurityHelper.toBytes(new StringBuffer(username).append('-').append(passwordHash.getPassword())); + encryptionParamValue = password; } } - if (groupId == null || groupId.length < 1) { + if (encryptionParamValue == null || encryptionParamValue.length < 1) { return null; } if (privateKeyEntry != null) { - // Then save the encrypted group - String encryptedGroupId = encryptString(privateKeyEntry, groupId); - bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_GROUP_ID, encryptedGroupId); + PasswordHash passwordHash = SecurityHelper.getPasswordHash(encryptionParamValue); - bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD, Base64.encodeToString(passwordHash.getPassword(), Base64.DEFAULT)); + // Then save the encrypted secret key + String encryptedSecretKey = encryptString(privateKeyEntry, passwordHash.getPassword()); + bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_SECRET_KEY, encryptedSecretKey); bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, Base64.encodeToString(passwordHash.getSalt(), Base64.DEFAULT)); // Finally, save the pioneer user @@ -618,7 +605,7 @@ public Bundle saveUserGroup(String userName, char[] password, LoginResponseData } finally { SecurityHelper.clearArray(password); - SecurityHelper.clearArray(groupId); + SecurityHelper.clearArray(encryptionParamValue); } } @@ -633,22 +620,29 @@ public void logout() { logoutSession(); allSettings.registerANM(""); allSettings.savePreviousFetchIndex("0"); - DrishtiApplication.getInstance().getRepository().deleteRepository(); + configuration.getDrishtiApplication().getRepository().deleteRepository(); } public void logoutSession() { session().expire(); + clearApplicationPasswordReference(); ON_LOGOUT.notifyListeners(true); } + private void clearApplicationPasswordReference() { + + SecurityHelper.clearArray(configuration.getDrishtiApplication().getPassword()); + configuration.getDrishtiApplication().setPassword(null); + } + public boolean hasSessionExpired() { return session().hasExpired(); } - protected void setupContextForLogin(char[] password) { + protected void setupContextForLogin(byte[] password) { session().start(session().lengthInMilliseconds()); configuration.getDrishtiApplication().setPassword(password); - session().setPassword(SecurityHelper.toBytes(password)); + session().setPassword(password); } protected Session session() { diff --git a/opensrp-app/src/main/java/org/smartregister/util/Session.java b/opensrp-app/src/main/java/org/smartregister/util/Session.java index b315dc52f..063c8336d 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/Session.java +++ b/opensrp-app/src/main/java/org/smartregister/util/Session.java @@ -53,6 +53,7 @@ public boolean hasExpired() { public void expire() { SecurityHelper.clearArray(this.password); + this.password = null; setSessionExpiryTimeTo(new Date().getTime() - 1); } diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java index 51a969643..b91005bc0 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java @@ -38,7 +38,7 @@ public abstract class DrishtiApplication extends Application { protected Locale locale = null; protected Context context; protected Repository repository; - private char[] password; + private byte[] password; private String username; public static synchronized X getInstance() { @@ -117,17 +117,17 @@ public Repository getRepository() { return repository; } - public char[] getPassword() { + public byte[] getPassword() { if (password == null) { String username = context.userService().getAllSharedPreferences().fetchRegisteredANM(); - password = context.userService().getGroupId(username); + password = context.userService().getAccountSecretKey(username); } return password; } - public void setPassword(char[] password) { + public void setPassword(byte[] password) { this.password = password; } diff --git a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java index 4f6cf82aa..bcb81d73f 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java @@ -209,7 +209,7 @@ public void logoutCurrentUser() { when(allSharedPreferences.fetchRegisteredANM()).thenReturn("user X"); - userService.processLoginResponseDataForUser("user X", "password Y".toCharArray(), userInfo); + userService.processLoginResponseDataForUser("user X", userInfo); verify(allSettings).registerANM("user X"); } diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java index df7313f5a..2f651d613 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java @@ -81,7 +81,7 @@ public void getPassword() { AllSharedPreferences allSharedPreferences = Mockito.spy(drishtiApplication.getContext().userService().getAllSharedPreferences()); ReflectionHelpers.setField(drishtiApplication.getContext().userService(), "allSharedPreferences", allSharedPreferences); Mockito.doReturn(username).when(allSharedPreferences).fetchRegisteredANM(); - Mockito.doReturn(password).when(userService).getGroupId(Mockito.eq(username)); + Mockito.doReturn(password).when(userService).getAccountSecretKey(Mockito.eq(username)); Assert.assertEquals(password, drishtiApplication.getPassword()); } From 326e5e3d460842c675ce0a76b0dfbc50ecc21526 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Mon, 20 Jul 2020 18:35:18 +0300 Subject: [PATCH 53/70] Refactor authentication logic - Separate local device authentication from database encryption/authentication - Refactor all password refs to byte array - Fix tests Signed-off-by: Martin Ndegwa --- .../smartregister/account/AccountHelper.java | 5 +- .../domain/jsonmapping/User.java | 14 +-- .../login/task/RemoteLoginTask.java | 2 +- .../repository/AllSharedPreferences.java | 11 ++ .../security/SecurityHelper.java | 8 ++ .../smartregister/service/UserService.java | 37 ++++-- .../view/activity/DrishtiApplication.java | 3 +- .../org/smartregister/TestApplication.java | 4 - .../repository/mock/RepositoryMock.java | 2 +- .../service/UserServiceTest.java | 118 ++++++++---------- .../view/activity/DrishtiApplicationTest.java | 2 +- 11 files changed, 111 insertions(+), 95 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java index 853191dcf..4ea12ef3b 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java @@ -128,7 +128,8 @@ public static String getOAuthToken(String accountName, String accountType, Strin * @param authToken token to invalidate */ public static void invalidateAuthToken(String accountType, String authToken) { - accountManager.invalidateAuthToken(accountType, authToken); + if (authToken != null) + accountManager.invalidateAuthToken(accountType, authToken); } @@ -140,7 +141,7 @@ public static void invalidateAuthToken(String accountType, String authToken) { */ public static String getCachedOAuthToken(String accountName, String accountType, String authTokenType) { Account account = getOauthAccountByNameAndType(accountName, accountType); - return accountManager.peekAuthToken(account, authTokenType); + return account != null ? accountManager.peekAuthToken(account, authTokenType) : null; } /** diff --git a/opensrp-app/src/main/java/org/smartregister/domain/jsonmapping/User.java b/opensrp-app/src/main/java/org/smartregister/domain/jsonmapping/User.java index 1e2e65b59..5ece59bba 100644 --- a/opensrp-app/src/main/java/org/smartregister/domain/jsonmapping/User.java +++ b/opensrp-app/src/main/java/org/smartregister/domain/jsonmapping/User.java @@ -16,7 +16,7 @@ public class User extends BaseEntity { private String username; - private String password; + private char[] password; private String salt; @@ -36,14 +36,14 @@ public User(String baseEntityId) { super(baseEntityId); } - public User(String baseEntityId, String username, String password, String salt) { + public User(String baseEntityId, String username, char[] password, String salt) { super(baseEntityId); this.username = username; this.password = password; this.salt = salt; } - public User(String baseEntityId, String username, String password, String salt, String status, List roles, List permissions) { + public User(String baseEntityId, String username, char[] password, String salt, String status, List roles, List permissions) { super(baseEntityId); this.username = username; this.password = password; @@ -53,7 +53,7 @@ public User(String baseEntityId, String username, String password, String salt, this.permissions = permissions; } - public User(String baseEntityId, String username, String password, String preferredName, String salt, String status, List roles, + public User(String baseEntityId, String username, char[] password, String preferredName, String salt, String status, List roles, List permissions) { super(baseEntityId); this.username = username; @@ -73,11 +73,11 @@ public void setUsername(String username) { this.username = username; } - public String getPassword() { + public char[] getPassword() { return password; } - public void setPassword(String password) { + public void setPassword(char[] password) { this.password = password; } @@ -172,7 +172,7 @@ public User withUsername(String username) { return this; } - public User withPassword(String password) { + public User withPassword(char[] password) { this.password = password; return this; } diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index 261ebafc6..a48fe8a24 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -116,7 +116,7 @@ protected LoginResponse doInBackground(Void... params) { mAccountManager.notifyAccountAuthenticated(account); } - if (getOpenSRPContext().userService().getAccountSecretKey(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { + if (getOpenSRPContext().userService().getDecryptedPreferenceValue(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { publishProgress(R.string.loading_client_settings); diff --git a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java index 284d8e468..131f60857 100644 --- a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java @@ -43,6 +43,7 @@ public class AllSharedPreferences { private static final String PEER_TO_PEER_SYNC_LAST_FOREIGN_PROCESSED_RECORD = "PEER_TO_PEER_SYNC_LAST_FOREIGN_PROCESSED_RECORD"; public static final String MANIFEST_VERSION = "MANIFEST_VERSION"; public static final String FORMS_VERSION = "FORMS_VERSION"; + private static final String ENCRYPTED_PASSPHRASE_KEY = "ENCRYPTED_PASSPHRASE_KEY"; private SharedPreferences preferences; public AllSharedPreferences(SharedPreferences preferences) { @@ -348,5 +349,15 @@ public SharedPreferences getPreferences() { return preferences; } + public String getPassphrase(String username) { + return preferences.getString(ENCRYPTED_PASSPHRASE_KEY + username, null); + } + + public void savePassphrase(String username, String passphrase) { + if (username != null) { + preferences.edit().putString(ENCRYPTED_PASSPHRASE_KEY + username, passphrase).commit(); + } + } + } diff --git a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java index f49b6bee9..81a7b7867 100644 --- a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java @@ -7,6 +7,7 @@ import net.sqlcipher.database.SQLiteDatabase; import org.apache.commons.codec.CharEncoding; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import java.nio.ByteBuffer; @@ -30,6 +31,7 @@ public class SecurityHelper { private static final Charset CHARSET = Charset.forName(CharEncoding.UTF_8); public static final int ITERATION_COUNT = 200048; + private static final int PASSPHRASE_SIZE = 32; /** * This method ensures that sensitive info can be collected for the edit text in a safer way @@ -160,4 +162,10 @@ public static byte[] nullSafeBase64Decode(String base64EncodedValue) { return null; } } + + public static char[] generateRandomPassphrase() { + + return RandomStringUtils.randomAlphanumeric(PASSPHRASE_SIZE).toCharArray(); + } + } diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 11b6c6f4c..c016f56f1 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -226,14 +226,14 @@ public boolean isUserInValidGroup(final String userName, final char[] password) try { // Compare stored password hash with provided password hash - storedHash = getAccountSecretKey(username); + storedHash = getDecryptedAccountValue(username, AccountHelper.INTENT_KEY.ACCOUNT_SECRET_KEY); passwordSalt = SecurityHelper.nullSafeBase64Decode(AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); passwordHash = SecurityHelper.hashPassword(getEncryptionParamValue(username, password), passwordSalt); if (storedHash != null && Arrays.equals(storedHash, passwordHash)) { - return isValidDBPassword(storedHash); + return isValidDBPassword(getDecryptedPreferenceValue(username)); } } catch (Exception e) { Timber.e(e); @@ -265,11 +265,11 @@ private boolean isValidDBPassword(byte[] password) { return DrishtiApplication.getInstance().getRepository().canUseThisPassword(password); } - public byte[] getAccountSecretKey(String userName) { + public byte[] getDecryptedAccountValue(String userName, String key) { if (keyStore != null && userName != null) { try { KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); - return getAccountSecretKey(userName, privateKeyEntry); + return getDecryptedAccountValue(userName, privateKeyEntry, key); } catch (Exception e) { Timber.e(e); } @@ -277,10 +277,10 @@ public byte[] getAccountSecretKey(String userName) { return null; } - private byte[] getAccountSecretKey(String userName, KeyStore.PrivateKeyEntry privateKeyEntry) { + private byte[] getDecryptedAccountValue(String userName, KeyStore.PrivateKeyEntry privateKeyEntry, String key) { if (privateKeyEntry != null) { - String encryptedSecretKey = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_SECRET_KEY, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); + String encryptedSecretKey = AccountHelper.getAccountManagerValue(key, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()); if (encryptedSecretKey != null) { try { @@ -293,6 +293,18 @@ private byte[] getAccountSecretKey(String userName, KeyStore.PrivateKeyEntry pri return null; } + public byte[] getDecryptedPreferenceValue(String userName) { + if (keyStore != null && userName != null) { + try { + KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); + return decryptString(privateKeyEntry, allSharedPreferences.getPassphrase(userName)); + } catch (Exception e) { + Timber.e(e); + } + } + return null; + } + /** * Checks whether the groupId for the provided user is the same as the first person to * successfully login @@ -305,8 +317,8 @@ public boolean isUserInPioneerGroup(String userName) { if (userName.equals(pioneerUser)) { return true; } else { - byte[] currentUserSecretKey = getAccountSecretKey(userName); - byte[] pioneerUserSecretKey = getAccountSecretKey(pioneerUser); + byte[] currentUserSecretKey = getDecryptedPreferenceValue(userName); + byte[] pioneerUserSecretKey = getDecryptedPreferenceValue(pioneerUser); if (currentUserSecretKey != null && Arrays.equals(pioneerUserSecretKey, currentUserSecretKey)) { return isValidDBPassword(currentUserSecretKey); @@ -354,7 +366,7 @@ public boolean localLoginWith(String userName) { try { - byte[] secretKey = getAccountSecretKey(userName); + byte[] secretKey = getDecryptedPreferenceValue(userName); setupContextForLogin(secretKey); if (!allSharedPreferences.fetchRegisteredANM().equalsIgnoreCase(userName)) { @@ -595,8 +607,13 @@ public Bundle saveUserCredentials(String userName, char[] password, LoginRespons bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_SECRET_KEY, encryptedSecretKey); bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, Base64.encodeToString(passwordHash.getSalt(), Base64.DEFAULT)); + //Save encrypted passphrase + if (StringUtils.isBlank(allSharedPreferences.getPassphrase(userName))) { + allSharedPreferences.savePassphrase(username, encryptString(privateKeyEntry, SecurityHelper.toBytes(SecurityHelper.generateRandomPassphrase()))); + } + // Finally, save the pioneer user - if (allSharedPreferences.fetchPioneerUser() == null) { + if (StringUtils.isBlank(allSharedPreferences.fetchPioneerUser())) { allSharedPreferences.savePioneerUser(username); } } diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java index b91005bc0..a5b8bac5b 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java @@ -14,6 +14,7 @@ import org.smartregister.Context; import org.smartregister.CoreLibrary; import org.smartregister.R; +import org.smartregister.account.AccountHelper; import org.smartregister.repository.DrishtiRepository; import org.smartregister.repository.Repository; import org.smartregister.sync.ClientProcessorForJava; @@ -121,7 +122,7 @@ public byte[] getPassword() { if (password == null) { String username = context.userService().getAllSharedPreferences().fetchRegisteredANM(); - password = context.userService().getAccountSecretKey(username); + password = context.userService().getDecryptedPreferenceValue(username); } return password; diff --git a/opensrp-app/src/test/java/org/smartregister/TestApplication.java b/opensrp-app/src/test/java/org/smartregister/TestApplication.java index c2b4c43e2..77265acdd 100644 --- a/opensrp-app/src/test/java/org/smartregister/TestApplication.java +++ b/opensrp-app/src/test/java/org/smartregister/TestApplication.java @@ -40,8 +40,4 @@ public void setContext(Context context) { this.context = context; } - @Override - public char[] getPassword() { - return new char[]{}; - } } diff --git a/opensrp-app/src/test/java/org/smartregister/repository/mock/RepositoryMock.java b/opensrp-app/src/test/java/org/smartregister/repository/mock/RepositoryMock.java index 0afbd399f..eb6ba6699 100644 --- a/opensrp-app/src/test/java/org/smartregister/repository/mock/RepositoryMock.java +++ b/opensrp-app/src/test/java/org/smartregister/repository/mock/RepositoryMock.java @@ -50,7 +50,7 @@ public SQLiteDatabase getWritableDatabase() { } @Override - public boolean canUseThisPassword(char[] password) { + public boolean canUseThisPassword(byte[] password) { return super.canUseThisPassword(password); } diff --git a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java index bcb81d73f..2e105d44c 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java @@ -1,8 +1,10 @@ package org.smartregister.service; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; @@ -24,6 +26,7 @@ import org.smartregister.repository.AllSettings; import org.smartregister.repository.AllSharedPreferences; import org.smartregister.repository.Repository; +import org.smartregister.security.SecurityHelper; import org.smartregister.sync.SaveANMLocationTask; import org.smartregister.sync.SaveANMTeamTask; import org.smartregister.sync.SaveUserInfoTask; @@ -42,7 +45,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -51,6 +53,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; import static org.smartregister.AllConstants.ENGLISH_LOCALE; import static org.smartregister.AllConstants.KANNADA_LOCALE; @@ -92,14 +95,25 @@ public class UserServiceTest extends BaseUnitTest { private LoginResponseData loginResponseData; + byte[] password = "Password Z".getBytes(); + + private String user = "johndoe"; + + @Mock + private DrishtiApplication drishtiApplication; + @Before - public void setUp() throws Exception { + public void setUp() { + initMocks(this); Whitebox.setInternalState(DrishtiApplication.getInstance(), "repository", repository); - userService = new UserService(allSettings, allSharedPreferences, httpAgent, session, configuration, saveANMLocationTask, saveUserInfoTask, saveANMTeamTask); + when(configuration.getDrishtiApplication()).thenReturn(drishtiApplication); + doReturn(repository).when(drishtiApplication).getRepository(); + userService = spy(new UserService(allSettings, allSharedPreferences, httpAgent, session, configuration, saveANMLocationTask, saveUserInfoTask, saveANMTeamTask)); userInfoJSON = "{\"locations\":{\"locationsHierarchy\":{\"map\":{\"cd4ed528-87cd-42ee-a175-5e7089521ebd\":{\"id\":\"cd4ed528-87cd-42ee-a175-5e7089521ebd\",\"label\":\"Pakistan\",\"node\":{\"locationId\":\"cd4ed528-87cd-42ee-a175-5e7089521ebd\",\"name\":\"Pakistan\",\"tags\":[\"Country\"],\"voided\":false},\"children\":{\"461f2be7-c95d-433c-b1d7-c68f272409d7\":{\"id\":\"461f2be7-c95d-433c-b1d7-c68f272409d7\",\"label\":\"Sindh\",\"node\":{\"locationId\":\"461f2be7-c95d-433c-b1d7-c68f272409d7\",\"name\":\"Sindh\",\"parentLocation\":{\"locationId\":\"cd4ed528-87cd-42ee-a175-5e7089521ebd\",\"name\":\"Pakistan\",\"voided\":false},\"tags\":[\"Province\"],\"voided\":false},\"children\":{\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\":{\"id\":\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\",\"label\":\"Karachi\",\"node\":{\"locationId\":\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\",\"name\":\"Karachi\",\"parentLocation\":{\"locationId\":\"461f2be7-c95d-433c-b1d7-c68f272409d7\",\"name\":\"Sindh\",\"parentLocation\":{\"locationId\":\"cd4ed528-87cd-42ee-a175-5e7089521ebd\",\"name\":\"Pakistan\",\"voided\":false},\"voided\":false},\"tags\":[\"City\"],\"voided\":false},\"children\":{\"60c21502-fec1-40f5-b77d-6df3f92771ce\":{\"id\":\"60c21502-fec1-40f5-b77d-6df3f92771ce\",\"label\":\"Baldia\",\"node\":{\"locationId\":\"60c21502-fec1-40f5-b77d-6df3f92771ce\",\"name\":\"Baldia\",\"parentLocation\":{\"locationId\":\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\",\"name\":\"Karachi\",\"parentLocation\":{\"locationId\":\"461f2be7-c95d-433c-b1d7-c68f272409d7\",\"name\":\"Sindh\",\"voided\":false},\"voided\":false},\"tags\":[\"Town\"],\"attributes\":{\"at1\":\"atttt1\"},\"voided\":false},\"parent\":\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\"}},\"parent\":\"461f2be7-c95d-433c-b1d7-c68f272409d7\"}},\"parent\":\"cd4ed528-87cd-42ee-a175-5e7089521ebd\"}}}},\"parentChildren\":{\"cd4ed528-87cd-42ee-a175-5e7089521ebd\":[\"461f2be7-c95d-433c-b1d7-c68f272409d7\"],\"461f2be7-c95d-433c-b1d7-c68f272409d7\":[\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\"],\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\":[\"60c21502-fec1-40f5-b77d-6df3f92771ce\"]}}},\"user\":{\"username\":\"demotest\",\"roles\":[\"Provider\",\"Authenticated\",\"Thrive Member\"],\"permissions\":[\"Delete Reports\",\"Remove Allergies\",\"Edit Cohorts\",\"View Unpublished Forms\",\"Patient Dashboard - View Patient Summary\",\"Add Relationships\",\"Edit Problems\",\"Remove Problems\",\"Patient Dashboard - View Forms Section\",\"Manage Report Designs\",\"Add Patient Programs\",\"Delete Orders\",\"Manage Identifier Types\",\"Manage Person Attribute Types\",\"Add Patient Identifiers\",\"View Visit Types\",\"View Patients\",\"Delete Concept Proposals\",\"View Identifier Types\",\"Delete Encounters\",\"View Global Properties\",\"Edit Visits\",\"View Concept Map Types\",\"Add Users\",\"Delete Cohorts\",\"Manage Scheduled Report Tasks\",\"View Concepts\",\"Edit Concept Proposals\",\"Add Visits\",\"Edit Patient Programs\",\"Manage Concept Datatypes\",\"Manage Indicator Definitions\",\"View Concept Proposals\",\"Add Allergies\",\"Edit Allergies\",\"Delete Observations\",\"View Roles\",\"Manage Address Templates\",\"Configure Visits\",\"Manage Data Set Definitions\",\"View Concept Sources\",\"Patient Dashboard - View Regimen Section\",\"View Calculations\",\"Manage Encounter Roles\",\"Delete People\",\"Edit Report Objects\",\"View People\",\"Manage Concept Sources\",\"View Orders\",\"Manage Concept Map Types\",\"Delete Patient Programs\",\"Add Problems\",\"Edit People\",\"Manage Locations\",\"View Patient Programs\",\"View Field Types\",\"View Relationship Types\",\"Manage Visit Attribute Types\",\"Manage Order Types\",\"Manage TeamLocation Attribute Types\",\"Form Entry\",\"View Encounter Types\",\"View Encounter Roles\",\"Manage Programs\",\"Edit Reports\",\"View TeamLocation Attribute Types\",\"View Order Types\",\"Manage Relationship Types\",\"Manage Providers\",\"Manage Reports\",\"Manage Concept Classes\",\"Add Concept Proposals\",\"View Patient Identifiers\",\"View Privileges\",\"View Data Entry Statistics\",\"Patient Dashboard - View Graphs Section\",\"Manage Tokens\",\"Add Reports\",\"View Forms\",\"View Administration Functions\",\"Manage Relationships\",\"View Observations\",\"View Team\",\"Add Observations\",\"View Member\",\"View Report Objects\",\"Edit Relationships\",\"View Relationships\",\"Manage Scheduler\",\"View Allergies\",\"View Concept Reference Terms\",\"View Encounters\",\"Edit Patient Identifiers\",\"Edit Observations\",\"Delete Patients\",\"Delete Patient Identifiers\",\"View Person Attribute Types\",\"Add Encounters\",\"View Problems\",\"Manage FormEntry XSN\",\"View Visits\",\"Edit Team\",\"Manage Field Types\",\"Patient Dashboard - View Encounters Section\",\"Add Team\",\"Add Cohorts\",\"Add Patients\",\"Patient Dashboard - View Demographics Section\",\"Manage Concepts\",\"View Rule Definitions\",\"Add Orders\",\"Manage Visit Types\",\"Patient Dashboard - View Visits Section\",\"View Locations\",\"Manage Forms\",\"Edit Encounters\",\"Delete Relationships\",\"Manage Concept Reference Terms\",\"Add Report Objects\",\"Manage Alerts\",\"View Users\",\"Edit Patients\",\"Manage Concept Stop Words\",\"View Concept Classes\",\"View Patient Cohorts\",\"View Visit Attribute Types\",\"Manage TeamLocation Tags\",\"Manage Encounter Types\",\"View Concept Datatypes\",\"View Navigation Menu\",\"Delete Visits\",\"Add People\",\"Edit Orders\",\"Manage Concept Name tags\",\"Run Reports\",\"View Providers\",\"Patient Dashboard - View Overview Section\",\"Manage Cohort Definitions\",\"View Reports\",\"View Programs\",\"Delete Report Objects\",\"Manage Report Definitions\"],\"baseEntityId\":\"6637559e-ebf9-480a-9731-c47e16e95716\",\"preferredName\":\"Demo test User\",\"voided\":false},\"time\":{\"time\":\"2018-03-02 10:17:51\",\"timeZone\":\"Africa/Harare\"},\"team\":{\"identifier\":\"12345678\",\"person\":{\"gender\":\"F\",\"display\":\"MOH ZEIR Demo\",\"resourceVersion\":\"1.11\",\"dead\":false,\"uuid\":\"12481a02-9a78-4c45-9ead-ddf24d14b19d\",\"birthdateEstimated\":false,\"deathdateEstimated\":false,\"attributes\":[],\"voided\":false,\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/person/12481a02-9a78-4c45-9ead-ddf24d14b19d\"},{\"rel\":\"full\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/person/12481a02-9a78-4c45-9ead-ddf24d14b19d?v\\u003dfull\"}],\"preferredName\":{\"display\":\"MOH ZEIR Demo\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/person/12481a02-9a78-4c45-9ead-ddf24d14b19d/name/4ab4a8b9-3723-44a8-8733-815ee6d05ef7\"}],\"uuid\":\"4ab4a8b9-3723-44a8-8733-815ee6d05ef7\"}},\"teamMemberId\":1.0,\"patients\":[],\"resourceVersion\":\"1.8\",\"location\":[{\"parentLocation\":{\"display\":\"Fort Jameson\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/25089a50-0cf0-47e8-8bfe-fecabed92530\"}],\"uuid\":\"25089a50-0cf0-47e8-8bfe-fecabed92530\"},\"display\":\"Happy Kids Clinic\",\"resourceVersion\":\"1.9\",\"uuid\":\"42abc582-6658-488b-922e-7be500c070f3\",\"tags\":[{\"display\":\"Health Centre Urban\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/locationtag/86c5e41b-08f0-495d-9130-170556c05041\"}],\"uuid\":\"86c5e41b-08f0-495d-9130-170556c05041\"},{\"display\":\"Health Facility\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/locationtag/4d9fce9d-c83a-46aa-b1d9-121da6176758\"}],\"uuid\":\"4d9fce9d-c83a-46aa-b1d9-121da6176758\"}],\"name\":\"Happy Kids Clinic\",\"retired\":false,\"attributes\":[{\"display\":\"dhis_ou_id: k2SgIKwkSh1\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42abc582-6658-488b-922e-7be500c070f3/attribute/01ec1f7c-e061-4f37-9d2c-ce1c7fe99c36\"}],\"uuid\":\"01ec1f7c-e061-4f37-9d2c-ce1c7fe99c36\"}],\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42abc582-6658-488b-922e-7be500c070f3\"},{\"rel\":\"full\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42abc582-6658-488b-922e-7be500c070f3?v\\u003dfull\"}],\"childLocations\":[{\"display\":\"Happy Kids Clinic: Zone 1\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42b88545-7ebb-4e11-8d1a-3d3a924c8af4\"}],\"uuid\":\"42b88545-7ebb-4e11-8d1a-3d3a924c8af4\"},{\"display\":\"Happy Kids Clinic: Zone 2\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/8a40cd7e-b8d4-4c6e-b88f-a77272fec630\"}],\"uuid\":\"8a40cd7e-b8d4-4c6e-b88f-a77272fec630\"},{\"display\":\"Happy Kids Clinic: Zone 3\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/5e79ae00-5a69-4814-aace-30e4717f823a\"}],\"uuid\":\"5e79ae00-5a69-4814-aace-30e4717f823a\"},{\"display\":\"Happy Kids Clinic: Zone 4\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/e79ff5bc-b6ff-46bc-9bbf-0cedc7d6c4c7\"}],\"uuid\":\"e79ff5bc-b6ff-46bc-9bbf-0cedc7d6c4c7\"}]}],\"team\":{\"teamName\":\"Demo\",\"dateCreated\":\"2017-04-06T09:21:39.000+0200\",\"display\":\"Demo\",\"resourceVersion\":\"1.8\",\"location\":{\"parentLocation\":{\"display\":\"Fort Jameson\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/25089a50-0cf0-47e8-8bfe-fecabed92530\"}],\"uuid\":\"25089a50-0cf0-47e8-8bfe-fecabed92530\"},\"display\":\"Happy Kids Clinic\",\"resourceVersion\":\"1.9\",\"uuid\":\"42abc582-6658-488b-922e-7be500c070f3\",\"tags\":[{\"display\":\"Health Centre Urban\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/locationtag/86c5e41b-08f0-495d-9130-170556c05041\"}],\"uuid\":\"86c5e41b-08f0-495d-9130-170556c05041\"},{\"display\":\"Health Facility\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/locationtag/4d9fce9d-c83a-46aa-b1d9-121da6176758\"}],\"uuid\":\"4d9fce9d-c83a-46aa-b1d9-121da6176758\"}],\"name\":\"Happy Kids Clinic\",\"retired\":false,\"attributes\":[{\"display\":\"dhis_ou_id: k2SgIKwkSh1\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42abc582-6658-488b-922e-7be500c070f3/attribute/01ec1f7c-e061-4f37-9d2c-ce1c7fe99c36\"}],\"uuid\":\"01ec1f7c-e061-4f37-9d2c-ce1c7fe99c36\"}],\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42abc582-6658-488b-922e-7be500c070f3\"},{\"rel\":\"full\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42abc582-6658-488b-922e-7be500c070f3?v\\u003dfull\"}],\"childLocations\":[{\"display\":\"Happy Kids Clinic: Zone 1\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42b88545-7ebb-4e11-8d1a-3d3a924c8af4\"}],\"uuid\":\"42b88545-7ebb-4e11-8d1a-3d3a924c8af4\"},{\"display\":\"Happy Kids Clinic: Zone 2\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/8a40cd7e-b8d4-4c6e-b88f-a77272fec630\"}],\"uuid\":\"8a40cd7e-b8d4-4c6e-b88f-a77272fec630\"},{\"display\":\"Happy Kids Clinic: Zone 3\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/5e79ae00-5a69-4814-aace-30e4717f823a\"}],\"uuid\":\"5e79ae00-5a69-4814-aace-30e4717f823a\"},{\"display\":\"Happy Kids Clinic: Zone 4\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/e79ff5bc-b6ff-46bc-9bbf-0cedc7d6c4c7\"}],\"uuid\":\"e79ff5bc-b6ff-46bc-9bbf-0cedc7d6c4c7\"}]},\"teamIdentifier\":\"Demo\",\"uuid\":\"7bfb4bb3-2689-404c-a5d4-f5cbe1aea9c4\"},\"isTeamLead\":true,\"uuid\":\"6ea953fb-46a2-4415-ae53-299ce909894b\"}}"; loginResponseData = AssetHandler.jsonStringToJava(userInfoJSON, LoginResponseData.class); } + @Test public void shouldUseHttpAgentToDoRemoteLoginCheck() { @@ -107,7 +121,7 @@ public void shouldUseHttpAgentToDoRemoteLoginCheck() { User userObject = new User(); userObject.setUsername("user"); - userObject.setPassword("password Y"); + userObject.setPassword(SecurityHelper.toChars(password)); userInfo.user = userObject; LoginResponse loginResponse = LoginResponse.SUCCESS.withPayload(userInfo); @@ -115,18 +129,18 @@ public void shouldUseHttpAgentToDoRemoteLoginCheck() { when(configuration.dristhiBaseURL()).thenReturn("http://dristhi_base_url"); String httpAuthenticateUrl = "http://dristhi_base_url/security/authenticate"; String user = "user"; - String password = "password Y"; + char[] password = "password Y".toCharArray(); when(httpAgent.urlCanBeAccessWithGivenCredentials( httpAuthenticateUrl, user, - password.toCharArray())).thenReturn(loginResponse); + password)).thenReturn(loginResponse); when(allSharedPreferences.fetchRegisteredANM()).thenReturn("user"); - userService.isValidRemoteLogin(user, password.toCharArray()); + userService.isValidRemoteLogin(user, password); - verify(httpAgent).urlCanBeAccessWithGivenCredentials(httpAuthenticateUrl, user, password.toCharArray()); + verify(httpAgent).urlCanBeAccessWithGivenCredentials(httpAuthenticateUrl, user, password); } @Test @@ -163,19 +177,19 @@ public void shouldSaveANMLocation() { public void shouldConsiderALocalLoginValid() { // When Username Matches Registered User And Password Matches The One In DB when(allSharedPreferences.fetchRegisteredANM()).thenReturn("ANM X"); - when(repository.canUseThisPassword("password Z".toCharArray())).thenReturn(true); + when(repository.canUseThisPassword(password)).thenReturn(true); - assertTrue(userService.isValidLocalLogin("ANM X", "password Z".toCharArray())); + assertTrue(userService.isValidLocalLogin("ANM X", password)); verify(allSharedPreferences).fetchRegisteredANM(); - verify(repository).canUseThisPassword("password Z".toCharArray()); + verify(repository).canUseThisPassword(password); } @Test public void shouldConsiderALocalLoginInvalidWhenRegisteredUserDoesNotMatch() { when(allSharedPreferences.fetchRegisteredANM()).thenReturn("ANM X"); - assertFalse(userService.isValidLocalLogin("SOME OTHER ANM", "password".toCharArray())); + assertFalse(userService.isValidLocalLogin("SOME OTHER ANM", "password".getBytes())); verify(allSharedPreferences).fetchRegisteredANM(); verifyZeroInteractions(repository); @@ -184,34 +198,36 @@ public void shouldConsiderALocalLoginInvalidWhenRegisteredUserDoesNotMatch() { @Test public void shouldConsiderALocalLoginInvalidWhenRegisteredUserMatchesButNotThePassword() { when(allSharedPreferences.fetchRegisteredANM()).thenReturn("ANM X"); - when(repository.canUseThisPassword("password Z".toCharArray())).thenReturn(false); + when(repository.canUseThisPassword(password)).thenReturn(false); - assertFalse(userService.isValidLocalLogin("ANM X", "password Z".toCharArray())); + assertFalse(userService.isValidLocalLogin("ANM X", password)); verify(allSharedPreferences).fetchRegisteredANM(); - verify(repository).canUseThisPassword("password Z".toCharArray()); + verify(repository).canUseThisPassword(password); } @Test public void shouldRegisterANewUser() { - when(configuration.getDrishtiApplication()).thenReturn(new DrishtiApplication() { - @Override - public void logoutCurrentUser() { - // Nothing to cleanup - } - }); + when(configuration.getDrishtiApplication()).thenReturn(drishtiApplication); LoginResponseData userInfo = new LoginResponseData(); + String newUsername = "user X"; + User user = new User(); - user.setUsername("user X"); - user.setPassword("password Y"); + user.setUsername(newUsername); + user.setPassword("password Y".toCharArray()); userInfo.user = user; - when(allSharedPreferences.fetchRegisteredANM()).thenReturn("user X"); + when(allSharedPreferences.fetchRegisteredANM()).thenReturn("user Z"); + ArgumentCaptor usernameCaptor = ArgumentCaptor.forClass(String.class); + + userService.processLoginResponseDataForUser(newUsername, userInfo); - userService.processLoginResponseDataForUser("user X", userInfo); + verify(allSharedPreferences).updateANMUserName(usernameCaptor.capture()); + String value = usernameCaptor.getValue(); + Assert.assertNotNull(value); + Assert.assertEquals(newUsername, value); - verify(allSettings).registerANM("user X"); } @Test @@ -316,7 +332,7 @@ public void testValidateDeviceTimeSameTimeTimeAndTimeZone() { public void testValidateStoredServerTimeZoneForNullServerTimeZoneReturnsError() { when(allSharedPreferences.fetchServerTimeZone()).thenReturn(null); assertEquals(TimeStatus.ERROR, userService.validateStoredServerTimeZone()); - verify(allSharedPreferences).saveForceRemoteLogin(true); + verify(allSharedPreferences).saveForceRemoteLogin(true, user); } @Test @@ -325,7 +341,7 @@ public void testValidateStoredServerTimeZoneForDifferentTimeZoneServerTimeZoneRe TimeZone.setDefault(TimeZone.getTimeZone("GMT")); assertEquals(TimeStatus.TIMEZONE_MISMATCH, userService.validateStoredServerTimeZone()); - verify(allSharedPreferences).saveForceRemoteLogin(true); + verify(allSharedPreferences).saveForceRemoteLogin(true, user); } @@ -334,7 +350,7 @@ public void testValidateStoredServerTimeZoneForSameTimeTimeAndTimeZone() { when(allSharedPreferences.fetchServerTimeZone()).thenReturn("Africa/Nairobi"); TimeZone.setDefault(TimeZone.getTimeZone("Africa/Nairobi")); assertEquals(TimeStatus.OK, userService.validateStoredServerTimeZone()); - verify(allSharedPreferences, never()).saveForceRemoteLogin(true); + verify(allSharedPreferences, never()).saveForceRemoteLogin(true, user); } @@ -348,19 +364,13 @@ public void testIsUserInValidGroupForValidUserAndPassword() throws Exception { Whitebox.setInternalState(userService, "keyStore", keyStore); Whitebox.setInternalState(keyStore, "initialized", true); Whitebox.setInternalState(keyStore, "keyStoreSpi", keyStoreSpi); - String user = "johndoe"; when(keyStore.containsAlias(user)).thenReturn(true); KeyStore.PrivateKeyEntry privateKeyEntry = PowerMockito.mock(KeyStore.PrivateKeyEntry.class); when(keyStore.getEntry(user, null)).thenReturn(privateKeyEntry); - String password = UUID.randomUUID().toString(); - when(allSharedPreferences.fetchEncryptedPassword(user)).thenReturn(password); - when(allSharedPreferences.fetchEncryptedGroupId(user)).thenReturn(password); userService = spy(userService); - doReturn(password).when(userService).decryptString(privateKeyEntry, password); + doReturn(password).when(userService).decryptString(privateKeyEntry, "RandomSECURE_TEXT"); when(repository.canUseThisPassword(password)).thenReturn(true); - assertTrue(userService.isUserInValidGroup(user, password)); - verify(allSharedPreferences).fetchEncryptedPassword(user); - verify(allSharedPreferences).fetchEncryptedGroupId(user); + assertTrue(userService.isUserInValidGroup(user, SecurityHelper.toChars(password))); verify(repository).canUseThisPassword(password); } @@ -375,38 +385,11 @@ public void testIsUserInValidGroupShouldReturnFalseOnError() throws Exception { KeyStore.PrivateKeyEntry privateKeyEntry = PowerMockito.mock(KeyStore.PrivateKeyEntry.class); when(keyStore.getEntry(user, null)).thenReturn(privateKeyEntry); String password = UUID.randomUUID().toString(); - when(allSharedPreferences.fetchEncryptedPassword(user)).thenReturn(password); - when(allSharedPreferences.fetchEncryptedGroupId(user)).thenReturn(password); - assertFalse(userService.isUserInValidGroup(user, password)); - verify(allSharedPreferences).fetchEncryptedPassword(user); - verify(allSharedPreferences, never()).fetchEncryptedGroupId(user); + assertFalse(userService.isUserInValidGroup(user, password.toCharArray())); + // verify(allSharedPreferences, never()).fetchEncryptedGroupId(user); verifyZeroInteractions(repository); } - @Test - public void testGetGroupIdShouldReturnNullOnError() throws Exception { - Whitebox.setInternalState(userService, "keyStore", keyStore); - Whitebox.setInternalState(keyStore, "initialized", true); - Whitebox.setInternalState(keyStore, "keyStoreSpi", keyStoreSpi); - assertNull(userService.getGroupId("johndoe")); - } - - @Test - public void testGetGroupIdShouldReturnGroupId() throws Exception { - userService = spy(userService); - Whitebox.setInternalState(userService, "keyStore", keyStore); - Whitebox.setInternalState(keyStore, "initialized", true); - Whitebox.setInternalState(keyStore, "keyStoreSpi", keyStoreSpi); - String password = UUID.randomUUID().toString(); - String user = "johndoe"; - when(keyStore.containsAlias(user)).thenReturn(true); - KeyStore.PrivateKeyEntry privateKeyEntry = PowerMockito.mock(KeyStore.PrivateKeyEntry.class); - when(keyStore.getEntry(user, null)).thenReturn(privateKeyEntry); - when(allSharedPreferences.fetchEncryptedGroupId(user)).thenReturn(password); - doReturn("pass123").when(userService).decryptString(privateKeyEntry, password); - assertEquals("pass123", userService.getGroupId(user)); - } - @Test public void testIsUserInPioneerGroupShouldReturnTrueForPioneerUser() throws Exception { userService = spy(userService); @@ -418,7 +401,6 @@ public void testIsUserInPioneerGroupShouldReturnTrueForPioneerUser() throws Exce when(keyStore.containsAlias(user)).thenReturn(true); KeyStore.PrivateKeyEntry privateKeyEntry = PowerMockito.mock(KeyStore.PrivateKeyEntry.class); when(keyStore.getEntry(user, null)).thenReturn(privateKeyEntry); - when(allSharedPreferences.fetchEncryptedGroupId(user)).thenReturn(password); when(allSharedPreferences.fetchPioneerUser()).thenReturn(user); assertTrue(userService.isUserInPioneerGroup(user)); } @@ -430,4 +412,4 @@ public void testIsUserInPioneerGroupShouldReturnFalseForOthers() throws Exceptio } -} +} \ No newline at end of file diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java index 2f651d613..736fe045e 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java @@ -81,7 +81,7 @@ public void getPassword() { AllSharedPreferences allSharedPreferences = Mockito.spy(drishtiApplication.getContext().userService().getAllSharedPreferences()); ReflectionHelpers.setField(drishtiApplication.getContext().userService(), "allSharedPreferences", allSharedPreferences); Mockito.doReturn(username).when(allSharedPreferences).fetchRegisteredANM(); - Mockito.doReturn(password).when(userService).getAccountSecretKey(Mockito.eq(username)); + Mockito.doReturn(password).when(userService).getDecryptedPreferenceValue(Mockito.eq(username)); Assert.assertEquals(password, drishtiApplication.getPassword()); } From 95cf026eff890437a2845935d96151f47c3aca6e Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Mon, 20 Jul 2020 18:58:50 +0300 Subject: [PATCH 54/70] Fix failing test --- .../smartregister/view/activity/SecuredActivityTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/SecuredActivityTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/SecuredActivityTest.java index 2b7f3bb7d..1bc707797 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/SecuredActivityTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/SecuredActivityTest.java @@ -51,7 +51,7 @@ * Created by Ephraim Kigamba - nek.eam@gmail.com on 14-07-2020. */ @Config(application = SecuredActivityTest.TestP2pApplication.class) -public class SecuredActivityTest extends BaseRobolectricUnitTest { +public class SecuredActivityTest extends BaseRobolectricUnitTest { private SecuredActivity securedActivity; @@ -70,7 +70,7 @@ public void setUp() { // Make sure the user is logged in Session session = ReflectionHelpers.getField(CoreLibrary.getInstance().context().userService(), "session"); - session.setPassword(""); + session.setPassword("".getBytes()); session.start(360 * 60 * 1000); org.mockito.MockitoAnnotations.initMocks(this); @@ -95,7 +95,7 @@ public void tearDown() throws Exception { @Test public void onCreateShouldCallOnCreationAndAddLogoutListener() { - List>> listeners = ReflectionHelpers.getField(Event.ON_LOGOUT, "listeners"); + List>> listeners = ReflectionHelpers.getField(Event.ON_LOGOUT, "listeners"); listeners.clear(); controller = Robolectric.buildActivity(SecuredActivityImpl.class); @@ -105,7 +105,7 @@ public void onCreateShouldCallOnCreationAndAddLogoutListener() { ReflectionHelpers.callInstanceMethod(Activity.class, securedActivity, "performCreate", from(Bundle.class, null)); Mockito.verify(securedActivity).onCreation(); - listeners = ReflectionHelpers.getField(Event.ON_LOGOUT, "listeners"); + listeners = ReflectionHelpers.getField(Event.ON_LOGOUT, "listeners"); Assert.assertEquals(1, listeners.size()); } From 624da51115736d0596f212341e7477ed1ffa85e3 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 21 Jul 2020 06:45:16 +0300 Subject: [PATCH 55/70] :bug: Fix verify authorization bug - Legacy Verify authorization shows disable user message prematurely - Refactor getEncryptedValue method name - Clean up unused method/tests --- .../login/task/RemoteLoginTask.java | 2 +- .../org/smartregister/service/HTTPAgent.java | 11 +++++-- .../smartregister/service/UserService.java | 24 +++----------- .../view/activity/DrishtiApplication.java | 3 +- .../service/UserServiceTest.java | 31 ------------------- .../view/activity/DrishtiApplicationTest.java | 4 +-- 6 files changed, 18 insertions(+), 57 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index a48fe8a24..c189668ea 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -116,7 +116,7 @@ protected LoginResponse doInBackground(Void... params) { mAccountManager.notifyAccountAuthenticated(account); } - if (getOpenSRPContext().userService().getDecryptedPreferenceValue(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { + if (getOpenSRPContext().userService().getDecryptedPassphraseValue(mUsername) != null && CoreLibrary.getInstance().getSyncConfiguration().isSyncSettings()) { publishProgress(R.string.loading_client_settings); diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index 40c43700e..c992396ca 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -928,8 +928,15 @@ public boolean verifyAuthorizationLegacy() { urlConnection = initializeHttp(baseUrl, true); - Timber.i("User not authorized. User access was revoked, will log off user"); - return false; + if (HttpStatus.SC_OK == urlConnection.getResponseCode()) { + return true; + + } else if (HttpStatus.SC_UNAUTHORIZED == urlConnection.getResponseCode()) { + + Timber.i("User not authorized. User access was revoked, will log off user"); + return false; + } + } else if (statusCode != HttpStatus.SC_OK) { Timber.w("Error occurred verifying authorization, User will not be logged off"); } else { diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index c016f56f1..07193e19a 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -233,7 +233,7 @@ public boolean isUserInValidGroup(final String userName, final char[] password) if (storedHash != null && Arrays.equals(storedHash, passwordHash)) { - return isValidDBPassword(getDecryptedPreferenceValue(username)); + return isValidDBPassword(getDecryptedPassphraseValue(username)); } } catch (Exception e) { Timber.e(e); @@ -293,7 +293,7 @@ private byte[] getDecryptedAccountValue(String userName, KeyStore.PrivateKeyEntr return null; } - public byte[] getDecryptedPreferenceValue(String userName) { + public byte[] getDecryptedPassphraseValue(String userName) { if (keyStore != null && userName != null) { try { KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); @@ -317,8 +317,8 @@ public boolean isUserInPioneerGroup(String userName) { if (userName.equals(pioneerUser)) { return true; } else { - byte[] currentUserSecretKey = getDecryptedPreferenceValue(userName); - byte[] pioneerUserSecretKey = getDecryptedPreferenceValue(pioneerUser); + byte[] currentUserSecretKey = getDecryptedPassphraseValue(userName); + byte[] pioneerUserSecretKey = getDecryptedPassphraseValue(pioneerUser); if (currentUserSecretKey != null && Arrays.equals(pioneerUserSecretKey, currentUserSecretKey)) { return isValidDBPassword(currentUserSecretKey); @@ -338,20 +338,6 @@ public LoginResponse fetchUserDetails(String accessToken) { return loginResponse; } - public LoginResponse isValidRemoteLogin(String userName, char[] password) { - String requestURL; - - requestURL = configuration.dristhiBaseURL() + OPENSRP_AUTH_USER_URL_PATH; - - LoginResponse loginResponse = httpAgent.urlCanBeAccessWithGivenCredentials(requestURL, userName, password); - - if (loginResponse != null && loginResponse.equals(LoginResponse.SUCCESS)) { - saveUserCredentials(userName, password, loginResponse.payload()); - } - - return loginResponse; - } - public AllSharedPreferences getAllSharedPreferences() { return allSharedPreferences; } @@ -366,7 +352,7 @@ public boolean localLoginWith(String userName) { try { - byte[] secretKey = getDecryptedPreferenceValue(userName); + byte[] secretKey = getDecryptedPassphraseValue(userName); setupContextForLogin(secretKey); if (!allSharedPreferences.fetchRegisteredANM().equalsIgnoreCase(userName)) { diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java index a5b8bac5b..b7c6941cb 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java @@ -14,7 +14,6 @@ import org.smartregister.Context; import org.smartregister.CoreLibrary; import org.smartregister.R; -import org.smartregister.account.AccountHelper; import org.smartregister.repository.DrishtiRepository; import org.smartregister.repository.Repository; import org.smartregister.sync.ClientProcessorForJava; @@ -122,7 +121,7 @@ public byte[] getPassword() { if (password == null) { String username = context.userService().getAllSharedPreferences().fetchRegisteredANM(); - password = context.userService().getDecryptedPreferenceValue(username); + password = context.userService().getDecryptedPassphraseValue(username); } return password; diff --git a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java index 2e105d44c..98b9f6786 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java @@ -16,7 +16,6 @@ import org.smartregister.CoreLibrary; import org.smartregister.DristhiConfiguration; import org.smartregister.SyncConfiguration; -import org.smartregister.domain.LoginResponse; import org.smartregister.domain.TimeStatus; import org.smartregister.domain.jsonmapping.LoginResponseData; import org.smartregister.domain.jsonmapping.Time; @@ -113,36 +112,6 @@ public void setUp() { loginResponseData = AssetHandler.jsonStringToJava(userInfoJSON, LoginResponseData.class); } - - @Test - public void shouldUseHttpAgentToDoRemoteLoginCheck() { - - LoginResponseData userInfo = new LoginResponseData(); - - User userObject = new User(); - userObject.setUsername("user"); - userObject.setPassword(SecurityHelper.toChars(password)); - userInfo.user = userObject; - - LoginResponse loginResponse = LoginResponse.SUCCESS.withPayload(userInfo); - - when(configuration.dristhiBaseURL()).thenReturn("http://dristhi_base_url"); - String httpAuthenticateUrl = "http://dristhi_base_url/security/authenticate"; - String user = "user"; - char[] password = "password Y".toCharArray(); - - when(httpAgent.urlCanBeAccessWithGivenCredentials( - httpAuthenticateUrl, - user, - password)).thenReturn(loginResponse); - - when(allSharedPreferences.fetchRegisteredANM()).thenReturn("user"); - - userService.isValidRemoteLogin(user, password); - - verify(httpAgent).urlCanBeAccessWithGivenCredentials(httpAuthenticateUrl, user, password); - } - @Test public void shouldGetANMLocation() { when(configuration.dristhiBaseURL()).thenReturn("http://opensrp_base_url"); diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java index 736fe045e..557b269d8 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java @@ -71,7 +71,7 @@ public void getRepository() { @Test public void getPassword() { String username = "anm"; - char[] password = "pwd".toCharArray(); + byte[] password = "pwd".getBytes(); drishtiApplication.onCreate(); @@ -81,7 +81,7 @@ public void getPassword() { AllSharedPreferences allSharedPreferences = Mockito.spy(drishtiApplication.getContext().userService().getAllSharedPreferences()); ReflectionHelpers.setField(drishtiApplication.getContext().userService(), "allSharedPreferences", allSharedPreferences); Mockito.doReturn(username).when(allSharedPreferences).fetchRegisteredANM(); - Mockito.doReturn(password).when(userService).getDecryptedPreferenceValue(Mockito.eq(username)); + Mockito.doReturn(password).when(userService).getDecryptedPassphraseValue(Mockito.eq(username)); Assert.assertEquals(password, drishtiApplication.getPassword()); } From e8fbe3485aff72ae6f129577e51759af2ab3361d Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 21 Jul 2020 16:39:26 +0300 Subject: [PATCH 56/70] :sparkles: Add database encryption migration - Add support for migrating database encryption keys - Add support to handle account removal gracefully by logging out user - Clean up code --- opensrp-app/build.gradle | 2 +- .../java/org/smartregister/CoreLibrary.java | 31 ++++++++++++++- .../repository/AllSharedPreferences.java | 19 +++++++--- .../smartregister/repository/Repository.java | 1 - .../smartregister/service/UserService.java | 38 ++++++++++++++----- .../helper/SyncSettingsServiceHelper.java | 2 - 6 files changed, 71 insertions(+), 22 deletions(-) diff --git a/opensrp-app/build.gradle b/opensrp-app/build.gradle index 0709af9eb..587c3c90a 100644 --- a/opensrp-app/build.gradle +++ b/opensrp-app/build.gradle @@ -93,7 +93,7 @@ android { versionName project.VERSION_NAME testInstrumentationRunner "android.test.InstrumentationTestRunner" buildConfigField "long", "BUILD_TIMESTAMP", System.currentTimeMillis() + "L" - buildConfigField "int", "ENCRYPTION_VERSION", '1' + buildConfigField "int", "DB_ENCRYPTION_VERSION", '1' } sourceSets { diff --git a/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java b/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java index fdc53fcc9..dd2192f9b 100644 --- a/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java +++ b/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java @@ -1,6 +1,8 @@ package org.smartregister; +import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.OnAccountsUpdateListener; import android.content.Intent; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -8,6 +10,7 @@ import android.text.TextUtils; import org.smartregister.account.AccountAuthenticatorXml; +import org.smartregister.account.AccountHelper; import org.smartregister.authorizer.P2PSyncAuthorizationService; import org.smartregister.p2p.P2PLibrary; import org.smartregister.pathevaluator.PathEvaluatorLibrary; @@ -15,14 +18,17 @@ import org.smartregister.repository.P2PReceiverTransferDao; import org.smartregister.repository.P2PSenderTransferDao; import org.smartregister.sync.P2PSyncFinishCallback; +import org.smartregister.util.SyncUtils; import org.smartregister.util.Utils; +import timber.log.Timber; + import static android.preference.PreferenceManager.getDefaultSharedPreferences; /** * Created by keyman on 31/07/17. */ -public class CoreLibrary { +public class CoreLibrary implements OnAccountsUpdateListener { private final Context context; @@ -159,8 +165,10 @@ public SyncConfiguration getSyncConfiguration() { } public AccountManager getAccountManager() { - if (accountManager == null) + if (accountManager == null) { accountManager = AccountManager.get(context.applicationContext()); + accountManager.addOnAccountsUpdatedListener(this, null, true); + } return accountManager; } @@ -208,4 +216,23 @@ private void sendPeerToPeerProcessingStatus(boolean status) { .sendBroadcast(intent); } + @Override + public void onAccountsUpdated(Account[] accounts) { + boolean accountExists = false; + for (Account account : accounts) { + Account currentUser = AccountHelper.getOauthAccountByNameAndType(CoreLibrary.getInstance().context().allSharedPreferences().fetchRegisteredANM(), authenticatorXml.getAccountType()); + if (account.equals(currentUser)) { + accountExists = true; + break; + } + } + + if (!accountExists) { + try { + (new SyncUtils(context.applicationContext())).logoutUser(); + } catch (Exception e) { + Timber.e(e); + } + } + } } diff --git a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java index 131f60857..473e49036 100644 --- a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java @@ -44,6 +44,7 @@ public class AllSharedPreferences { public static final String MANIFEST_VERSION = "MANIFEST_VERSION"; public static final String FORMS_VERSION = "FORMS_VERSION"; private static final String ENCRYPTED_PASSPHRASE_KEY = "ENCRYPTED_PASSPHRASE_KEY"; + private static final String DB_ENCRYPTION_VERSION = "DB_ENCRYPTION_VERSION"; private SharedPreferences preferences; public AllSharedPreferences(SharedPreferences preferences) { @@ -349,14 +350,20 @@ public SharedPreferences getPreferences() { return preferences; } - public String getPassphrase(String username) { - return preferences.getString(ENCRYPTED_PASSPHRASE_KEY + username, null); + public String getPassphrase() { + return preferences.getString(ENCRYPTED_PASSPHRASE_KEY, null); } - public void savePassphrase(String username, String passphrase) { - if (username != null) { - preferences.edit().putString(ENCRYPTED_PASSPHRASE_KEY + username, passphrase).commit(); - } + public void savePassphrase(String passphrase) { + preferences.edit().putString(ENCRYPTED_PASSPHRASE_KEY, passphrase).commit(); + } + + public int getDBEncryptionVersion() { + return preferences.getInt(DB_ENCRYPTION_VERSION, 0); + } + + public void setDBEncryptionVersion(int encryptionVersion) { + preferences.edit().putInt(DB_ENCRYPTION_VERSION, encryptionVersion).commit(); } } diff --git a/opensrp-app/src/main/java/org/smartregister/repository/Repository.java b/opensrp-app/src/main/java/org/smartregister/repository/Repository.java index 000e0d177..6463b66cc 100755 --- a/opensrp-app/src/main/java/org/smartregister/repository/Repository.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/Repository.java @@ -2,7 +2,6 @@ import android.content.Context; -import net.sqlcipher.DefaultDatabaseErrorHandler; import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteDatabaseHook; import net.sqlcipher.database.SQLiteException; diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 07193e19a..943e48a8d 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -8,6 +8,7 @@ import android.util.Base64; import org.apache.commons.lang3.StringUtils; +import org.smartregister.BuildConfig; import org.smartregister.CoreLibrary; import org.smartregister.DristhiConfiguration; import org.smartregister.SyncConfiguration; @@ -297,7 +298,7 @@ public byte[] getDecryptedPassphraseValue(String userName) { if (keyStore != null && userName != null) { try { KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); - return decryptString(privateKeyEntry, allSharedPreferences.getPassphrase(userName)); + return decryptString(privateKeyEntry, allSharedPreferences.getPassphrase()); } catch (Exception e) { Timber.e(e); } @@ -588,20 +589,13 @@ public Bundle saveUserCredentials(String userName, char[] password, LoginRespons PasswordHash passwordHash = SecurityHelper.getPasswordHash(encryptionParamValue); - // Then save the encrypted secret key + // Save the encrypted secret key for local login String encryptedSecretKey = encryptString(privateKeyEntry, passwordHash.getPassword()); bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_SECRET_KEY, encryptedSecretKey); bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, Base64.encodeToString(passwordHash.getSalt(), Base64.DEFAULT)); - //Save encrypted passphrase - if (StringUtils.isBlank(allSharedPreferences.getPassphrase(userName))) { - allSharedPreferences.savePassphrase(username, encryptString(privateKeyEntry, SecurityHelper.toBytes(SecurityHelper.generateRandomPassphrase()))); - } + updateSharedPreferences(username, privateKeyEntry); - // Finally, save the pioneer user - if (StringUtils.isBlank(allSharedPreferences.fetchPioneerUser())) { - allSharedPreferences.savePioneerUser(username); - } } } catch (Exception e) { Timber.e(e); @@ -615,6 +609,30 @@ public Bundle saveUserCredentials(String userName, char[] password, LoginRespons return bundle; } + private void updateSharedPreferences(String username, KeyStore.PrivateKeyEntry privateKeyEntry) throws Exception { + + if (StringUtils.isBlank(allSharedPreferences.fetchPioneerUser())) { + allSharedPreferences.savePioneerUser(username); + } + + if (allSharedPreferences.getDBEncryptionVersion() > 0 && BuildConfig.DB_ENCRYPTION_VERSION > allSharedPreferences.getDBEncryptionVersion()) { + + processDBEncryptionVersioning(privateKeyEntry); + + } else if (StringUtils.isBlank(allSharedPreferences.getPassphrase())) { + + processDBEncryptionVersioning(privateKeyEntry); + } + + } + + private void processDBEncryptionVersioning(KeyStore.PrivateKeyEntry privateKeyEntry) throws Exception { + byte[] passphrase = SecurityHelper.toBytes(SecurityHelper.generateRandomPassphrase()); + DrishtiApplication.getInstance().getRepository().getReadableDatabase().changePassword(SecurityHelper.toChars(passphrase)); + allSharedPreferences.savePassphrase(encryptString(privateKeyEntry, passphrase)); + allSharedPreferences.setDBEncryptionVersion(BuildConfig.DB_ENCRYPTION_VERSION); + } + public boolean hasARegisteredUser() { return !allSharedPreferences.fetchRegisteredANM().equals(""); } diff --git a/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java b/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java index d5554b7a8..54c87d097 100644 --- a/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/sync/helper/SyncSettingsServiceHelper.java @@ -30,8 +30,6 @@ public class SyncSettingsServiceHelper { private HTTPAgent httpAgent; private String baseUrl; - private String username; - private String password; private AllSharedPreferences sharedPreferences; public SyncSettingsServiceHelper(String baseUrl, HTTPAgent httpAgent) { From ee673a71de8ae75a60aa388b4a48ffda08986c68 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 21 Jul 2020 18:22:11 +0300 Subject: [PATCH 57/70] Fix failing build - Migrate to newer version of plan evaluator library --- opensrp-app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opensrp-app/build.gradle b/opensrp-app/build.gradle index 587c3c90a..2a61e3e87 100644 --- a/opensrp-app/build.gradle +++ b/opensrp-app/build.gradle @@ -236,7 +236,7 @@ dependencies { implementation 'org.smartregister:opensrp-client-utils:0.0.2-SNAPSHOT' - implementation 'org.smartregister:opensrp-plan-evaluator:0.0.13-SNAPSHOT' + implementation 'org.smartregister:opensrp-plan-evaluator:0.0.19-SNAPSHOT' implementation 'xerces:xercesImpl:2.12.0' } From 962abac5324c961c84fe551ca268a9c640c680a0 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 21 Jul 2020 20:14:15 +0300 Subject: [PATCH 58/70] :bug: Fix crash on fresh install - Fix app crashing for fresh installs ` --- .../java/org/smartregister/CoreLibrary.java | 24 ++++++++++--------- .../smartregister/service/UserService.java | 15 +++++++----- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java b/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java index dd2192f9b..221e71cbe 100644 --- a/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java +++ b/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java @@ -219,19 +219,21 @@ private void sendPeerToPeerProcessingStatus(boolean status) { @Override public void onAccountsUpdated(Account[] accounts) { boolean accountExists = false; - for (Account account : accounts) { - Account currentUser = AccountHelper.getOauthAccountByNameAndType(CoreLibrary.getInstance().context().allSharedPreferences().fetchRegisteredANM(), authenticatorXml.getAccountType()); - if (account.equals(currentUser)) { - accountExists = true; - break; + Account currentUser = AccountHelper.getOauthAccountByNameAndType(CoreLibrary.getInstance().context().allSharedPreferences().fetchRegisteredANM(), authenticatorXml.getAccountType()); + if (currentUser != null) { + for (Account account : accounts) { + if (account.equals(currentUser)) { + accountExists = true; + break; + } } - } - if (!accountExists) { - try { - (new SyncUtils(context.applicationContext())).logoutUser(); - } catch (Exception e) { - Timber.e(e); + if (!accountExists) { + try { + (new SyncUtils(context.applicationContext())).logoutUser(); + } catch (Exception e) { + Timber.e(e); + } } } } diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 943e48a8d..b3b79f63b 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -354,13 +354,16 @@ public boolean localLoginWith(String userName) { try { byte[] secretKey = getDecryptedPassphraseValue(userName); - setupContextForLogin(secretKey); + if (secretKey != null) { + setupContextForLogin(secretKey); - if (!allSharedPreferences.fetchRegisteredANM().equalsIgnoreCase(userName)) { - allSharedPreferences.updateANMUserName(userName); - } + if (!allSharedPreferences.fetchRegisteredANM().equalsIgnoreCase(userName)) { + allSharedPreferences.updateANMUserName(userName); + } - DrishtiApplication.getInstance().getRepository().getReadableDatabase(); + DrishtiApplication.getInstance().getRepository().getReadableDatabase(); + } else + return false; } catch (Exception e) { Timber.e(e); @@ -628,9 +631,9 @@ private void updateSharedPreferences(String username, KeyStore.PrivateKeyEntry p private void processDBEncryptionVersioning(KeyStore.PrivateKeyEntry privateKeyEntry) throws Exception { byte[] passphrase = SecurityHelper.toBytes(SecurityHelper.generateRandomPassphrase()); - DrishtiApplication.getInstance().getRepository().getReadableDatabase().changePassword(SecurityHelper.toChars(passphrase)); allSharedPreferences.savePassphrase(encryptString(privateKeyEntry, passphrase)); allSharedPreferences.setDBEncryptionVersion(BuildConfig.DB_ENCRYPTION_VERSION); + DrishtiApplication.getInstance().getRepository().getReadableDatabase().changePassword(SecurityHelper.toChars(passphrase)); } public boolean hasARegisteredUser() { From 23832d0ca755d2479325cbcd572a97e37a84a0f3 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Wed, 22 Jul 2020 14:16:27 +0300 Subject: [PATCH 59/70] Refactor authentication logic - Move credentials helper methods to helper class - Fix automatic logout an Account Manager account removal --- gradle.properties | 3 +- .../java/org/smartregister/CoreLibrary.java | 42 +++++--- .../account/AccountAuthenticatorXml.java | 10 +- .../smartregister/account/AccountHelper.java | 4 +- .../login/task/RemoteLoginTask.java | 4 +- .../repository/AllSharedPreferences.java | 8 +- .../smartregister/service/UserService.java | 86 ++++------------ .../smartregister/util/CredentialsHelper.java | 98 +++++++++++++++++++ .../java/org/smartregister/util/Utils.java | 2 +- .../view/activity/DrishtiApplication.java | 16 ++- .../smartregister/service/HTTPAgentTest.java | 2 +- 11 files changed, 174 insertions(+), 101 deletions(-) create mode 100644 opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java diff --git a/gradle.properties b/gradle.properties index ac928efda..7c839ee8f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ -VERSION_NAME=2.0.4-SNAPSHOT +VERSION_NAME=2.0.4-SNAPSHOT +>>>>>>> Refactor authentication logic VERSION_CODE=1 GROUP=org.smartregister POM_SETTING_DESCRIPTION=OpenSRP Client Core Application diff --git a/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java b/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java index 221e71cbe..556611357 100644 --- a/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java +++ b/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java @@ -9,8 +9,8 @@ import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; +import org.apache.commons.lang3.StringUtils; import org.smartregister.account.AccountAuthenticatorXml; -import org.smartregister.account.AccountHelper; import org.smartregister.authorizer.P2PSyncAuthorizationService; import org.smartregister.p2p.P2PLibrary; import org.smartregister.pathevaluator.PathEvaluatorLibrary; @@ -18,7 +18,6 @@ import org.smartregister.repository.P2PReceiverTransferDao; import org.smartregister.repository.P2PSenderTransferDao; import org.smartregister.sync.P2PSyncFinishCallback; -import org.smartregister.util.SyncUtils; import org.smartregister.util.Utils; import timber.log.Timber; @@ -218,23 +217,36 @@ private void sendPeerToPeerProcessingStatus(boolean status) { @Override public void onAccountsUpdated(Account[] accounts) { - boolean accountExists = false; - Account currentUser = AccountHelper.getOauthAccountByNameAndType(CoreLibrary.getInstance().context().allSharedPreferences().fetchRegisteredANM(), authenticatorXml.getAccountType()); - if (currentUser != null) { - for (Account account : accounts) { - if (account.equals(currentUser)) { - accountExists = true; - break; + try { + String loggedInUser = context.allSharedPreferences().fetchRegisteredANM(); + + if (!StringUtils.isBlank(loggedInUser)) { + + boolean accountExists = false; + + for (Account account : accounts) { + if (account.name.equals(context.allSharedPreferences().fetchRegisteredANM())) { + accountExists = true; + break; + } } - } - if (!accountExists) { - try { - (new SyncUtils(context.applicationContext())).logoutUser(); - } catch (Exception e) { - Timber.e(e); + if (!accountExists) { + + Intent intent = new Intent(context.applicationContext(), getSyncConfiguration().getAuthenticationActivity()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addCategory(Intent.CATEGORY_HOME); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + + context.applicationContext().startActivity(intent); + context.userService().forceRemoteLogin(context.allSharedPreferences().fetchRegisteredANM()); + context.userService().logoutSession(); + } } + } catch (Exception e) { + Timber.e(e); } } } diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticatorXml.java b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticatorXml.java index f6a2956ed..724034038 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticatorXml.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountAuthenticatorXml.java @@ -5,15 +5,15 @@ */ public class AccountAuthenticatorXml { private String accountType; - private String accountName; + private String accountLabel; private int icon; public String getAccountType() { return accountType; } - public String getAccountName() { - return accountName; + public String getAccountLabel() { + return accountLabel; } public int getIcon() { @@ -24,8 +24,8 @@ public void setAccountType(String accountType) { this.accountType = accountType; } - public void setAccountName(String accountName) { - this.accountName = accountName; + public void setAccountLabel(String accountLabel) { + this.accountLabel = accountLabel; } public void setIcon(int icon) { diff --git a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java index 4ea12ef3b..69faf52a9 100644 --- a/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/account/AccountHelper.java @@ -46,9 +46,9 @@ public static final class INTENT_KEY { public final static String AUTH_TYPE = "AUTH_TYPE"; public final static String ACCOUNT_NAME = "ACCOUNT_NAME"; public final static String IS_NEW_ACCOUNT = "IS_NEW_ACCOUNT"; - public final static String ACCOUNT_SECRET_KEY = "ACCOUNT_SECRET_KEY"; public final static String ACCOUNT_REFRESH_TOKEN = "ACCOUNT_REFRESH_TOKEN"; - public final static String ACCOUNT_PASSWORD_SALT = "ACCOUNT_PASSWORD_SALT"; + public final static String ACCOUNT_LOCAL_PASSWORD_SALT = "ACCOUNT_LOCAL_PASSWORD_SALT"; + public final static String ACCOUNT_LOCAL_PASSWORD = "ACCOUNT_LOCAL_PASSWORD"; } public static final class TOKEN_TYPE { diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index c189668ea..c1ac8883e 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -107,8 +107,8 @@ protected LoginResponse doInBackground(Void... params) { mAccountManager.addAccountExplicitly(account, response.getRefreshToken(), userData); mAccountManager.setAuthToken(account, mLoginView.getAuthTokenType(), response.getAccessToken()); mAccountManager.setPassword(account, response.getRefreshToken()); - mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_SECRET_KEY, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_SECRET_KEY)); - mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT)); + mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD)); + mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD_SALT, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD_SALT)); mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_NAME, userData.getString(AccountHelper.INTENT_KEY.ACCOUNT_NAME)); mAccountManager.setUserData(account, AccountHelper.INTENT_KEY.ACCOUNT_REFRESH_TOKEN, response.getRefreshToken()); diff --git a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java index 473e49036..01995694d 100644 --- a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java @@ -350,12 +350,12 @@ public SharedPreferences getPreferences() { return preferences; } - public String getPassphrase() { - return preferences.getString(ENCRYPTED_PASSPHRASE_KEY, null); + public String getPassphrase(String encryptionParam) { + return preferences.getString(new StringBuffer(ENCRYPTED_PASSPHRASE_KEY).append('_').append(encryptionParam).toString(), null); } - public void savePassphrase(String passphrase) { - preferences.edit().putString(ENCRYPTED_PASSPHRASE_KEY, passphrase).commit(); + public void savePassphrase(String passphrase, String encryptionParam) { + preferences.edit().putString(new StringBuffer(ENCRYPTED_PASSPHRASE_KEY).append('_').append(encryptionParam).toString(), passphrase).commit(); } public int getDBEncryptionVersion() { diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index b3b79f63b..6646132f5 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -8,12 +8,10 @@ import android.util.Base64; import org.apache.commons.lang3.StringUtils; -import org.smartregister.BuildConfig; import org.smartregister.CoreLibrary; import org.smartregister.DristhiConfiguration; -import org.smartregister.SyncConfiguration; -import org.smartregister.SyncFilter; import org.smartregister.account.AccountHelper; +import org.smartregister.util.CredentialsHelper; import org.smartregister.domain.LoginResponse; import org.smartregister.domain.Response; import org.smartregister.domain.TimeStatus; @@ -227,14 +225,14 @@ public boolean isUserInValidGroup(final String userName, final char[] password) try { // Compare stored password hash with provided password hash - storedHash = getDecryptedAccountValue(username, AccountHelper.INTENT_KEY.ACCOUNT_SECRET_KEY); + storedHash = DrishtiApplication.getInstance().credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.LOCAL_AUTH); - passwordSalt = SecurityHelper.nullSafeBase64Decode(AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); - passwordHash = SecurityHelper.hashPassword(getEncryptionParamValue(username, password), passwordSalt); + passwordSalt = SecurityHelper.nullSafeBase64Decode(AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD_SALT, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); + passwordHash = SecurityHelper.hashPassword(password, passwordSalt); if (storedHash != null && Arrays.equals(storedHash, passwordHash)) { - return isValidDBPassword(getDecryptedPassphraseValue(username)); + return isValidDBPassword(DrishtiApplication.getInstance().credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH)); } } catch (Exception e) { Timber.e(e); @@ -249,19 +247,6 @@ public boolean isUserInValidGroup(final String userName, final char[] password) return false; } - private char[] getEncryptionParamValue(String username, char[] password) { - - char[] encryptionParamValue = password; - SyncFilter syncFilter = CoreLibrary.getInstance().getSyncConfiguration().getEncryptionParam(); - - if (SyncFilter.TEAM.equals(syncFilter) || SyncFilter.TEAM_ID.equals(syncFilter)) { - encryptionParamValue = allSharedPreferences.fetchDefaultTeamId(username).toCharArray(); - } else if (SyncFilter.LOCATION.equals(syncFilter) || SyncFilter.LOCATION_ID.equals(syncFilter)) { - encryptionParamValue = allSharedPreferences.fetchDefaultLocalityId(username).toCharArray(); - } - return encryptionParamValue; - } - private boolean isValidDBPassword(byte[] password) { return DrishtiApplication.getInstance().getRepository().canUseThisPassword(password); } @@ -298,7 +283,7 @@ public byte[] getDecryptedPassphraseValue(String userName) { if (keyStore != null && userName != null) { try { KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); - return decryptString(privateKeyEntry, allSharedPreferences.getPassphrase()); + return decryptString(privateKeyEntry, allSharedPreferences.getPassphrase(CoreLibrary.getInstance().getSyncConfiguration().getEncryptionParam().name())); } catch (Exception e) { Timber.e(e); } @@ -562,8 +547,6 @@ public Bundle saveUserCredentials(String userName, char[] password, LoginRespons if (keyStore != null && username != null) { - char[] encryptionParamValue = null; - try { KeyStore.PrivateKeyEntry privateKeyEntry = createUserKeyPair(username); @@ -572,70 +555,39 @@ public Bundle saveUserCredentials(String userName, char[] password, LoginRespons return null; } - SyncConfiguration syncConfiguration = CoreLibrary.getInstance().getSyncConfiguration(); - if (syncConfiguration.getEncryptionParam() != null) { - SyncFilter syncFilter = syncConfiguration.getEncryptionParam(); - if (SyncFilter.TEAM.equals(syncFilter) || SyncFilter.TEAM_ID.equals(syncFilter)) { - encryptionParamValue = getUserDefaultTeamId(userInfo).toCharArray(); - } else if (SyncFilter.LOCATION.equals(syncFilter) || SyncFilter.LOCATION_ID.equals(syncFilter)) { - encryptionParamValue = getUserLocationId(userInfo).toCharArray(); - } else if (SyncFilter.PROVIDER.equals(syncFilter)) { - encryptionParamValue = password; - } - } - - if (encryptionParamValue == null || encryptionParamValue.length < 1) { + PasswordHash localAuthHash = DrishtiApplication.getInstance().credentialsProvider().generateLocalAuthCredentials(password); + if (localAuthHash == null) { return null; } if (privateKeyEntry != null) { - PasswordHash passwordHash = SecurityHelper.getPasswordHash(encryptionParamValue); - // Save the encrypted secret key for local login - String encryptedSecretKey = encryptString(privateKeyEntry, passwordHash.getPassword()); - bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_SECRET_KEY, encryptedSecretKey); - bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_PASSWORD_SALT, Base64.encodeToString(passwordHash.getSalt(), Base64.DEFAULT)); + String encryptedLocalAuthHash = encryptString(privateKeyEntry, localAuthHash.getPassword()); + bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD, encryptedLocalAuthHash); + bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD_SALT, Base64.encodeToString(localAuthHash.getSalt(), Base64.DEFAULT)); - updateSharedPreferences(username, privateKeyEntry); + //Save db credentials + byte[] passphrase = DrishtiApplication.getInstance().credentialsProvider().generateDBCredentials(SecurityHelper.toChars(localAuthHash.getPassword()), userInfo); + DrishtiApplication.getInstance().credentialsProvider().saveCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH, encryptString(privateKeyEntry, passphrase)); + //Save pioneer user + if (StringUtils.isBlank(allSharedPreferences.fetchPioneerUser())) { + allSharedPreferences.savePioneerUser(username); + } } + } catch (Exception e) { Timber.e(e); } finally { SecurityHelper.clearArray(password); - SecurityHelper.clearArray(encryptionParamValue); } } return bundle; } - private void updateSharedPreferences(String username, KeyStore.PrivateKeyEntry privateKeyEntry) throws Exception { - - if (StringUtils.isBlank(allSharedPreferences.fetchPioneerUser())) { - allSharedPreferences.savePioneerUser(username); - } - - if (allSharedPreferences.getDBEncryptionVersion() > 0 && BuildConfig.DB_ENCRYPTION_VERSION > allSharedPreferences.getDBEncryptionVersion()) { - - processDBEncryptionVersioning(privateKeyEntry); - - } else if (StringUtils.isBlank(allSharedPreferences.getPassphrase())) { - - processDBEncryptionVersioning(privateKeyEntry); - } - - } - - private void processDBEncryptionVersioning(KeyStore.PrivateKeyEntry privateKeyEntry) throws Exception { - byte[] passphrase = SecurityHelper.toBytes(SecurityHelper.generateRandomPassphrase()); - allSharedPreferences.savePassphrase(encryptString(privateKeyEntry, passphrase)); - allSharedPreferences.setDBEncryptionVersion(BuildConfig.DB_ENCRYPTION_VERSION); - DrishtiApplication.getInstance().getRepository().getReadableDatabase().changePassword(SecurityHelper.toChars(passphrase)); - } - public boolean hasARegisteredUser() { return !allSharedPreferences.fetchRegisteredANM().equals(""); } diff --git a/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java b/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java new file mode 100644 index 000000000..20cb847c6 --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java @@ -0,0 +1,98 @@ +package org.smartregister.util; + +import org.smartregister.BuildConfig; +import org.smartregister.Context; +import org.smartregister.CoreLibrary; +import org.smartregister.SyncConfiguration; +import org.smartregister.SyncFilter; +import org.smartregister.account.AccountHelper; +import org.smartregister.domain.jsonmapping.LoginResponseData; +import org.smartregister.repository.AllSharedPreferences; +import org.smartregister.security.PasswordHash; +import org.smartregister.security.SecurityHelper; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +/** + * Created by ndegwamartin on 22/07/2020. + */ +public class CredentialsHelper { + private Context context; + private AllSharedPreferences allSharedPreferences; + + public CredentialsHelper(Context context) { + this.context = context; + this.allSharedPreferences = context.allSharedPreferences(); + } + + public byte[] getCredentials(String type) { + + String username = context.userService().getAllSharedPreferences().fetchRegisteredANM(); + + if (CREDENTIALS_TYPE.DB_AUTH.equals(type)) { + + return context.userService().getDecryptedPassphraseValue(username); + + } else if (CREDENTIALS_TYPE.LOCAL_AUTH.equals(type)) { + + return context.userService().getDecryptedAccountValue(username, AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD); + + } + + return null; + } + + + public void saveCredentials(String type, String encryptedPassphrase) { + + if (CREDENTIALS_TYPE.DB_AUTH.equals(type)) { + + allSharedPreferences.savePassphrase(encryptedPassphrase, CoreLibrary.getInstance().getSyncConfiguration().getEncryptionParam().name()); + allSharedPreferences.setDBEncryptionVersion(BuildConfig.DB_ENCRYPTION_VERSION); + + + } else if (CREDENTIALS_TYPE.LOCAL_AUTH.equals(type)) { + + //saved in Account Manager by caller + } + + } + + public PasswordHash generateLocalAuthCredentials(char[] password) throws InvalidKeySpecException, NoSuchAlgorithmException { + + if (password == null) + return null; + + return SecurityHelper.getPasswordHash(password); + } + + public byte[] generateDBCredentials(char[] password, LoginResponseData userInfo) { + + char[] encryptionParamValue = null; + + SyncConfiguration syncConfiguration = CoreLibrary.getInstance().getSyncConfiguration(); + if (syncConfiguration.getEncryptionParam() != null) { + SyncFilter syncFilter = syncConfiguration.getEncryptionParam(); + if (SyncFilter.TEAM.equals(syncFilter) || SyncFilter.TEAM_ID.equals(syncFilter)) { + encryptionParamValue = context.userService().getUserDefaultTeamId(userInfo).toCharArray(); + } else if (SyncFilter.LOCATION.equals(syncFilter) || SyncFilter.LOCATION_ID.equals(syncFilter)) { + encryptionParamValue = context.userService().getUserLocationId(userInfo).toCharArray(); + } else if (SyncFilter.PROVIDER.equals(syncFilter)) { + encryptionParamValue = password; + } + } + + if (encryptionParamValue == null || encryptionParamValue.length < 1) { + return null; + } + + return SecurityHelper.toBytes(encryptionParamValue); + + } + + public class CREDENTIALS_TYPE { + public static final String LOCAL_AUTH = "local_auth"; + public static final String DB_AUTH = "db_auth"; + } +} diff --git a/opensrp-app/src/main/java/org/smartregister/util/Utils.java b/opensrp-app/src/main/java/org/smartregister/util/Utils.java index 1d91a1ce9..58831a5ef 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/Utils.java +++ b/opensrp-app/src/main/java/org/smartregister/util/Utils.java @@ -935,7 +935,7 @@ public static AccountAuthenticatorXml parseAuthenticatorXMLConfigData(Context co //Account Name int labelId = parser.getAttributeResourceValue(namespace, "label", 0); - authenticatorXml.setAccountName(context.getResources().getString(labelId)); + authenticatorXml.setAccountLabel(context.getResources().getString(labelId)); //Icon int iconImageResourceId = parser.getAttributeResourceValue(namespace, "icon", 0); diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java index b7c6941cb..f1752b42e 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java @@ -20,6 +20,7 @@ import org.smartregister.sync.P2PClassifier; import org.smartregister.util.BitmapImageCache; import org.smartregister.util.CrashLyticsTree; +import org.smartregister.util.CredentialsHelper; import org.smartregister.util.OpenSRPImageLoader; import java.io.File; @@ -40,6 +41,7 @@ public abstract class DrishtiApplication extends Application { protected Repository repository; private byte[] password; private String username; + private static CredentialsHelper credentialsHelper; public static synchronized X getInstance() { return (X) mInstance; @@ -117,11 +119,19 @@ public Repository getRepository() { return repository; } + public CredentialsHelper credentialsProvider() { + + if (credentialsHelper == null) { + credentialsHelper = new CredentialsHelper(context); + } + + return credentialsHelper; + } + public byte[] getPassword() { - if (password == null) { - String username = context.userService().getAllSharedPreferences().fetchRegisteredANM(); - password = context.userService().getDecryptedPassphraseValue(username); + if (password == null) { + password = credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); } return password; diff --git a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java index a20c03758..929470511 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/HTTPAgentTest.java @@ -170,7 +170,7 @@ public void setUp() { PowerMockito.when(CoreLibrary.getInstance()).thenReturn(coreLibrary); Mockito.doReturn(accountManager).when(coreLibrary).getAccountManager(); Mockito.doReturn(TEST_ACCOUNT_TYPE).when(accountAuthenticatorXml).getAccountType(); - Mockito.doReturn(TEST_USERNAME).when(accountAuthenticatorXml).getAccountName(); + Mockito.doReturn(TEST_USERNAME).when(accountAuthenticatorXml).getAccountLabel(); Mockito.doReturn(accountAuthenticatorXml).when(coreLibrary).getAccountAuthenticatorXml(); Mockito.doReturn(accountManager).when(coreLibrary).getAccountManager(); From abeacd7dc1148b96bd576e07f6453e5f187c8763 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Wed, 22 Jul 2020 22:06:06 +0300 Subject: [PATCH 60/70] DB Encryption migration feature :sparkles: - Add feature to support migration of DB encryption for devices in use - Unit tests - Fix bug: auto-logout inconsistent on account removal from account manager - Update autolog out alert message rendering --- opensrp-app/res/values-ar/strings.xml | 1 + opensrp-app/res/values-fr/strings.xml | 1 + opensrp-app/res/values/strings.xml | 3 +- .../java/org/smartregister/AllConstants.java | 2 + .../java/org/smartregister/CoreLibrary.java | 46 +++-- .../repository/AllSharedPreferences.java | 8 + .../security/SecurityHelper.java | 5 + .../smartregister/service/UserService.java | 56 +++++- .../smartregister/util/CredentialsHelper.java | 9 +- .../java/org/smartregister/util/Utils.java | 19 +- .../view/activity/BaseLoginActivity.java | 19 +- .../security/SecurityHelperTest.java | 13 +- .../util/CredentialsHelperTest.java | 189 ++++++++++++++++++ 13 files changed, 332 insertions(+), 39 deletions(-) create mode 100644 opensrp-app/src/test/java/org/smartregister/util/CredentialsHelperTest.java diff --git a/opensrp-app/res/values-ar/strings.xml b/opensrp-app/res/values-ar/strings.xml index 2dbd9bcb2..70a6219e4 100644 --- a/opensrp-app/res/values-ar/strings.xml +++ b/opensrp-app/res/values-ar/strings.xml @@ -415,4 +415,5 @@ %1$dس %2$dش + لقد تم تسجيل خروجك لأنه تم حذف حسابك من مدير الحساب diff --git a/opensrp-app/res/values-fr/strings.xml b/opensrp-app/res/values-fr/strings.xml index c5898c615..c8bdbfd0b 100644 --- a/opensrp-app/res/values-fr/strings.xml +++ b/opensrp-app/res/values-fr/strings.xml @@ -412,5 +412,6 @@ l\'addresse URL ne peut pas être vide. Synchronisation échouée. Vérifier configuration URL. Synchronisation échouée. Impossible de connecter au serveur. + Vous avez été déconnecté car votre compte a été supprimé du gestionnaire de compte. diff --git a/opensrp-app/res/values/strings.xml b/opensrp-app/res/values/strings.xml index 698bbba61..3adb09538 100644 --- a/opensrp-app/res/values/strings.xml +++ b/opensrp-app/res/values/strings.xml @@ -432,5 +432,6 @@ Uploads images to the OpenSRP server Manages user authentication and authorization Processes the records when using peer to peer for file sharing - + You have been logged off as your application database encryption version has changed. Please login in again to complete the upgrade. + You have been logged off as your account has been removed from the accounts manager. diff --git a/opensrp-app/src/main/java/org/smartregister/AllConstants.java b/opensrp-app/src/main/java/org/smartregister/AllConstants.java index 2a90b52b9..b33318e37 100644 --- a/opensrp-app/src/main/java/org/smartregister/AllConstants.java +++ b/opensrp-app/src/main/java/org/smartregister/AllConstants.java @@ -417,6 +417,8 @@ public static final class INTENT_KEY { public static final String IS_REMOTE_LOGIN = "is_remote_login"; public static final String TASK_GENERATED_EVENT = "task_generated_event"; public static final String TASK_GENERATED = "task_generated"; + public static final String DIALOG_TITLE = "dialog_title"; + public static final String DIALOG_MESSAGE = "dialog_message"; } public static final class REGISTER_FRAGMENT { diff --git a/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java b/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java index 556611357..60529b8a9 100644 --- a/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java +++ b/opensrp-app/src/main/java/org/smartregister/CoreLibrary.java @@ -18,6 +18,7 @@ import org.smartregister.repository.P2PReceiverTransferDao; import org.smartregister.repository.P2PSenderTransferDao; import org.smartregister.sync.P2PSyncFinishCallback; +import org.smartregister.util.CredentialsHelper; import org.smartregister.util.Utils; import timber.log.Timber; @@ -67,6 +68,15 @@ public static void init(Context context, SyncConfiguration syncConfiguration, lo if (instance == null) { instance = new CoreLibrary(context, syncConfiguration, options); buildTimeStamp = buildTimestamp; + checkPlatformMigrations(); + } + } + + + private static void checkPlatformMigrations() { + boolean shouldMigrate = CredentialsHelper.shouldMigrate(); + if (shouldMigrate && StringUtils.isNotBlank(instance.context().userService().getAllSharedPreferences().fetchPioneerUser())) {//Force remote login + Utils.logoutUser(instance.context(), instance.context().applicationContext().getString(R.string.new_db_encryption_version_migration)); } } @@ -217,36 +227,30 @@ private void sendPeerToPeerProcessingStatus(boolean status) { @Override public void onAccountsUpdated(Account[] accounts) { - try { - String loggedInUser = context.allSharedPreferences().fetchRegisteredANM(); + if (context.allSharedPreferences().getDBEncryptionVersion() > 0) { + try { + String loggedInUser = context.allSharedPreferences().fetchRegisteredANM(); - if (!StringUtils.isBlank(loggedInUser)) { + if (!StringUtils.isBlank(loggedInUser)) { - boolean accountExists = false; + boolean accountExists = false; - for (Account account : accounts) { - if (account.name.equals(context.allSharedPreferences().fetchRegisteredANM())) { - accountExists = true; - break; + for (Account account : accounts) { + if (account.name.equals(context.allSharedPreferences().fetchRegisteredANM())) { + accountExists = true; + break; + } } - } - - if (!accountExists) { - Intent intent = new Intent(context.applicationContext(), getSyncConfiguration().getAuthenticationActivity()); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addCategory(Intent.CATEGORY_HOME); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + if (!accountExists) { - context.applicationContext().startActivity(intent); - context.userService().forceRemoteLogin(context.allSharedPreferences().fetchRegisteredANM()); - context.userService().logoutSession(); + Utils.logoutUser(context, context.applicationContext().getString(R.string.account_removed)); + } } + } catch (Exception e) { + Timber.e(e); } - } catch (Exception e) { - Timber.e(e); } } } diff --git a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java index 01995694d..0e1bc7c5b 100644 --- a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java @@ -17,6 +17,7 @@ import static org.smartregister.AllConstants.DEFAULT_TEAM_ID_PREFIX; import static org.smartregister.AllConstants.DEFAULT_TEAM_PREFIX; import static org.smartregister.AllConstants.DRISHTI_BASE_URL; +import static org.smartregister.AllConstants.ENCRYPTED_GROUP_ID_PREFIX; import static org.smartregister.AllConstants.FORCE_REMOTE_LOGIN; import static org.smartregister.AllConstants.IS_SYNC_INITIAL_KEY; import static org.smartregister.AllConstants.IS_SYNC_IN_PROGRESS_PREFERENCE_KEY; @@ -84,6 +85,13 @@ public void saveServerTimeZone(String serverTimeZone) { preferences.edit().putString(SERVER_TIMEZONE, serverTimeZone).commit(); } + public String fetchEncryptedGroupId(String username) { + if (username != null) { + return preferences.getString(ENCRYPTED_GROUP_ID_PREFIX + username, null); + } + return null; + } + public String fetchPioneerUser() { return preferences.getString(PIONEER_USER, null); } diff --git a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java index 81a7b7867..d4adbe870 100644 --- a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java @@ -163,6 +163,11 @@ public static byte[] nullSafeBase64Decode(String base64EncodedValue) { } } + /** + * Generates random characters of the specified size + * + * @return a random array of alphanumeric chars + */ public static char[] generateRandomPassphrase() { return RandomStringUtils.randomAlphanumeric(PASSPHRASE_SIZE).toCharArray(); diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 6646132f5..ef1cf5a15 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -8,10 +8,10 @@ import android.util.Base64; import org.apache.commons.lang3.StringUtils; +import org.smartregister.BuildConfig; import org.smartregister.CoreLibrary; import org.smartregister.DristhiConfiguration; import org.smartregister.account.AccountHelper; -import org.smartregister.util.CredentialsHelper; import org.smartregister.domain.LoginResponse; import org.smartregister.domain.Response; import org.smartregister.domain.TimeStatus; @@ -29,6 +29,7 @@ import org.smartregister.sync.SaveANMTeamTask; import org.smartregister.sync.SaveUserInfoTask; import org.smartregister.util.AssetHandler; +import org.smartregister.util.CredentialsHelper; import org.smartregister.util.Session; import org.smartregister.view.activity.DrishtiApplication; @@ -568,13 +569,34 @@ public Bundle saveUserCredentials(String userName, char[] password, LoginRespons bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD_SALT, Base64.encodeToString(localAuthHash.getSalt(), Base64.DEFAULT)); //Save db credentials - byte[] passphrase = DrishtiApplication.getInstance().credentialsProvider().generateDBCredentials(SecurityHelper.toChars(localAuthHash.getPassword()), userInfo); - DrishtiApplication.getInstance().credentialsProvider().saveCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH, encryptString(privateKeyEntry, passphrase)); + if (CredentialsHelper.shouldMigrate()) { + + byte[] passphrase = DrishtiApplication.getInstance().credentialsProvider().generateDBCredentials(SecurityHelper.toChars(localAuthHash.getPassword()), userInfo); + byte[] oldPassword = allSharedPreferences.getDBEncryptionVersion() == 0 ? getGroupId(username) : DrishtiApplication.getInstance().credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); + + if (oldPassword != null && !Arrays.equals(passphrase, oldPassword)) { + try { + + DrishtiApplication.getInstance().getRepository().getReadableDatabase(SecurityHelper.toChars(oldPassword)).changePassword(SecurityHelper.toChars(passphrase)); + DrishtiApplication.getInstance().credentialsProvider().saveCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH, encryptString(privateKeyEntry, passphrase)); + + } catch (Exception e) { + Timber.e("Database encryption migration to version %s failed!!! ", BuildConfig.DB_ENCRYPTION_VERSION); + Timber.e(e); + } + + } else { + + DrishtiApplication.getInstance().credentialsProvider().saveCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH, encryptString(privateKeyEntry, passphrase)); + } + } //Save pioneer user if (StringUtils.isBlank(allSharedPreferences.fetchPioneerUser())) { allSharedPreferences.savePioneerUser(username); } + + allSharedPreferences.updateANMUserName(userName);//update current logged in user } } catch (Exception e) { @@ -749,4 +771,32 @@ private String encryptString(KeyStore.PrivateKeyEntry privateKeyEntry, byte[] pl public KeyStore getKeyStore() { return keyStore; } + + @Deprecated + public byte[] getGroupId(String userName) { + if (keyStore != null && userName != null) { + try { + KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName); + return getGroupId(userName, privateKeyEntry); + } catch (Exception e) { + Timber.e(e); + } + } + return null; + } + + @Deprecated + public byte[] getGroupId(String userName, KeyStore.PrivateKeyEntry privateKeyEntry) { + if (privateKeyEntry != null) { + String encryptedGroupId = allSharedPreferences.fetchEncryptedGroupId(userName); + if (encryptedGroupId != null) { + try { + return decryptString(privateKeyEntry, encryptedGroupId); + } catch (Exception e) { + Timber.e(e); + } + } + } + return null; + } } diff --git a/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java b/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java index 20cb847c6..a9573d902 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java @@ -26,9 +26,15 @@ public CredentialsHelper(Context context) { this.allSharedPreferences = context.allSharedPreferences(); } + public static boolean shouldMigrate() { + return CoreLibrary.getInstance().context().allSharedPreferences().getDBEncryptionVersion() == 0 || + (CoreLibrary.getInstance().context().allSharedPreferences().getDBEncryptionVersion() > 0 && + BuildConfig.DB_ENCRYPTION_VERSION > CoreLibrary.getInstance().context().allSharedPreferences().getDBEncryptionVersion()); + } + public byte[] getCredentials(String type) { - String username = context.userService().getAllSharedPreferences().fetchRegisteredANM(); + String username = allSharedPreferences.fetchRegisteredANM(); if (CREDENTIALS_TYPE.DB_AUTH.equals(type)) { @@ -51,7 +57,6 @@ public void saveCredentials(String type, String encryptedPassphrase) { allSharedPreferences.savePassphrase(encryptedPassphrase, CoreLibrary.getInstance().getSyncConfiguration().getEncryptionParam().name()); allSharedPreferences.setDBEncryptionVersion(BuildConfig.DB_ENCRYPTION_VERSION); - } else if (CREDENTIALS_TYPE.LOCAL_AUTH.equals(type)) { //saved in Account Manager by caller diff --git a/opensrp-app/src/main/java/org/smartregister/util/Utils.java b/opensrp-app/src/main/java/org/smartregister/util/Utils.java index 58831a5ef..5cbcdf62b 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/Utils.java +++ b/opensrp-app/src/main/java/org/smartregister/util/Utils.java @@ -99,6 +99,7 @@ import timber.log.Timber; import static android.content.Context.INPUT_METHOD_SERVICE; +import static org.smartregister.AllConstants.ACCOUNT_DISABLED; import static org.smartregister.util.Log.logError; @@ -898,13 +899,13 @@ public static Long tryParseLong(String value, long defaultValue) { } } - public static int calculatePercentage(long totalCount, long partialCount){ + public static int calculatePercentage(long totalCount, long partialCount) { if (totalCount < 1) { return 100; } else if (partialCount < 1) { return 0; } else { - return Math.round(( partialCount * 100f) / totalCount); + return Math.round((partialCount * 100f) / totalCount); } } @@ -950,4 +951,18 @@ public static AccountAuthenticatorXml parseAuthenticatorXMLConfigData(Context co return null; } } + + public static void logoutUser(org.smartregister.Context context, String message) { + Intent intent = new Intent(context.applicationContext(), CoreLibrary.getInstance().getSyncConfiguration().getAuthenticationActivity()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addCategory(Intent.CATEGORY_HOME); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + if (StringUtils.isNotBlank(message)) + intent.putExtra(AllConstants.INTENT_KEY.DIALOG_MESSAGE, message); + + context.applicationContext().startActivity(intent); + context.userService().forceRemoteLogin(context.allSharedPreferences().fetchRegisteredANM()); + context.userService().logoutSession(); + } } \ No newline at end of file diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java index eddf5a256..c370415db 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java @@ -26,6 +26,7 @@ import android.widget.TextView; import org.joda.time.DateTime; +import org.smartregister.AllConstants; import org.smartregister.R; import org.smartregister.account.AccountHelper; import org.smartregister.security.SecurityHelper; @@ -90,9 +91,12 @@ public boolean onOptionsItemSelected(MenuItem item) { protected void onResume() { super.onResume(); if (getIntent() != null) { - String logoffReason = getIntent().getStringExtra(ACCOUNT_DISABLED); + + String logoffReason = getIntent().hasExtra(ACCOUNT_DISABLED) ? getIntent().getStringExtra(ACCOUNT_DISABLED) : getIntent().getStringExtra(AllConstants.INTENT_KEY.DIALOG_MESSAGE); + String dialogTitle = getIntent().hasExtra(ACCOUNT_DISABLED) ? getString(R.string.account_disabled) : getIntent().getStringExtra(AllConstants.INTENT_KEY.DIALOG_TITLE); + if (logoffReason != null) { - showErrorDialog(R.string.account_disabled, logoffReason); + showErrorDialog(dialogTitle, logoffReason); } } } @@ -168,15 +172,14 @@ public void showErrorDialog(String message) { } public void showErrorDialog(@StringRes int title, String message) { + showErrorDialog(this.getString(title), message); + } + + public void showErrorDialog(String title, String message) { if (alertDialog == null) { alertDialog = new AlertDialog.Builder(this) - .setPositiveButton("OK", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - } - }).create(); + .setPositiveButton("OK", (dialogInterface, i) -> dialogInterface.dismiss()).create(); } alertDialog.setTitle(title); diff --git a/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java b/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java index d627c22c3..bd6264b7e 100644 --- a/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java @@ -4,6 +4,7 @@ import android.util.Base64; import org.apache.commons.codec.CharEncoding; +import org.apache.commons.lang3.StringUtils; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -16,6 +17,7 @@ import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import org.smartregister.BaseUnitTest; import java.io.UnsupportedEncodingException; import java.nio.charset.CharacterCodingException; @@ -32,7 +34,7 @@ @RunWith(PowerMockRunner.class) @PrepareForTest({Base64.class, SecretKeyFactory.class}) -public class SecurityHelperTest { +public class SecurityHelperTest extends BaseUnitTest { @Mock private Editable editable; @@ -50,7 +52,6 @@ public void setUp() { TEST_PASSWORD = "TEST_PASSWORD".toCharArray(); } - @Test public void testReadValueClearsEditableAfterReadingValue() { @@ -159,4 +160,12 @@ public void testGetPsswordHashReturnsHashedPasswordObject() throws Exception { Assert.assertNotNull(passwordHash.getPassword()); Assert.assertNotNull(passwordHash.getSalt()); } + + @Test + public void testGenerateRandomPassphraseGeneratesAlphanumericArray() { + char[] value = SecurityHelper.generateRandomPassphrase(); + Assert.assertNotNull(value); + Assert.assertTrue(StringUtils.isAlphanumeric(new StringBuilder().append(value).toString())); + Assert.assertEquals(32, value.length); + } } diff --git a/opensrp-app/src/test/java/org/smartregister/util/CredentialsHelperTest.java b/opensrp-app/src/test/java/org/smartregister/util/CredentialsHelperTest.java new file mode 100644 index 000000000..1d53a3f0f --- /dev/null +++ b/opensrp-app/src/test/java/org/smartregister/util/CredentialsHelperTest.java @@ -0,0 +1,189 @@ +package org.smartregister.util; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.rule.PowerMockRule; +import org.robolectric.RuntimeEnvironment; +import org.smartregister.BaseUnitTest; +import org.smartregister.BuildConfig; +import org.smartregister.Context; +import org.smartregister.CoreLibrary; +import org.smartregister.SyncConfiguration; +import org.smartregister.SyncFilter; +import org.smartregister.account.AccountHelper; +import org.smartregister.domain.jsonmapping.LoginResponseData; +import org.smartregister.repository.AllSharedPreferences; +import org.smartregister.security.SecurityHelper; +import org.smartregister.service.UserService; + +import java.util.Arrays; + +/** + * Created by ndegwamartin on 22/07/2020. + */ +@PrepareForTest({CoreLibrary.class, SecurityHelper.class}) +public class CredentialsHelperTest extends BaseUnitTest { + + @Mock + private CoreLibrary coreLibrary; + + @Mock + private Context context; + + @Mock + private UserService userService; + + @Mock + private SyncConfiguration syncConfiguration; + + @Mock + private AllSharedPreferences allSharedPreferences; + + @Mock + private LoginResponseData userInfo; + + private CredentialsHelper credentialsHelper; + + private static final String TEST_USERNAME = "demo"; + + private static final char[] TEST_DUMMY_PASSWORD = "test_password".toCharArray(); + + private static final String TEST_ENCRYPTED_PWD = "4794#25%&%34&"; + + private static final String TEST_LOCATION_ID = "3SF43-4AG3-3SUI44"; + + private static final String TEST_TEAM_ID = "48SG2-23B4F2-F442-F3F44"; + + @Rule + public PowerMockRule rule = new PowerMockRule(); + + @Before + public void setUp() { + + MockitoAnnotations.initMocks(this); + PowerMockito.mockStatic(CoreLibrary.class); + PowerMockito.when(CoreLibrary.getInstance()).thenReturn(coreLibrary); + PowerMockito.when(coreLibrary.context()).thenReturn(context); + PowerMockito.when(context.applicationContext()).thenReturn(RuntimeEnvironment.application); + + Mockito.doReturn(TEST_USERNAME).when(allSharedPreferences).fetchRegisteredANM(); + Mockito.doReturn(allSharedPreferences).when(context).allSharedPreferences(); + Mockito.doReturn(userService).when(context).userService(); + + Mockito.doReturn(syncConfiguration).when(coreLibrary).getSyncConfiguration(); + + credentialsHelper = new CredentialsHelper(context); + Assert.assertNotNull(credentialsHelper); + + } + + @Test + public void testShouldMigrateReturnsTrueForDBEncryptionVersionZero() { + boolean shouldMigrate = CredentialsHelper.shouldMigrate(); + Assert.assertTrue(shouldMigrate); + } + + @Test + public void testGetCredentialsInvokesGetDecryptedPassphraseValueWithCorrectValuesForDBAuth() { + + credentialsHelper.getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); + ArgumentCaptor usernameArgCaptor = ArgumentCaptor.forClass(String.class); + + Mockito.verify(userService, Mockito.times(1)).getDecryptedPassphraseValue(usernameArgCaptor.capture()); + Assert.assertEquals(TEST_USERNAME, usernameArgCaptor.getValue()); + } + + @Test + public void testGetCredentialsInvokesGetDecryptedPassphraseValueWithCorrectValuesForLocalAuth() { + + credentialsHelper.getCredentials(CredentialsHelper.CREDENTIALS_TYPE.LOCAL_AUTH); + + ArgumentCaptor usernameArgCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor keyArgCaptor = ArgumentCaptor.forClass(String.class); + + Mockito.verify(userService, Mockito.times(1)).getDecryptedAccountValue(usernameArgCaptor.capture(), keyArgCaptor.capture()); + + Assert.assertEquals(TEST_USERNAME, usernameArgCaptor.getValue()); + Assert.assertEquals(AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD, keyArgCaptor.getValue()); + } + + @Test + public void testSaveCredentialsUpdatesSharedPreferencesWithEncryptedPassphrase() { + + Mockito.doReturn(syncConfiguration).when(coreLibrary).getSyncConfiguration(); + Mockito.doReturn(SyncFilter.TEAM_ID).when(syncConfiguration).getEncryptionParam(); + + credentialsHelper.saveCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH, TEST_ENCRYPTED_PWD); + + ArgumentCaptor encryptionValueArgCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor encryptionParamArgCaptor = ArgumentCaptor.forClass(String.class); + + Mockito.verify(allSharedPreferences, Mockito.times(1)).savePassphrase(encryptionValueArgCaptor.capture(), encryptionParamArgCaptor.capture()); + + Assert.assertEquals(TEST_ENCRYPTED_PWD, encryptionValueArgCaptor.getValue()); + Assert.assertEquals(SyncFilter.TEAM_ID.name(), encryptionParamArgCaptor.getValue()); + } + + @Test + public void testSaveCredentialsUpdatesSharedPreferencesWithNewDBEncryptionVersion() { + + Mockito.doReturn(SyncFilter.LOCATION_ID).when(syncConfiguration).getEncryptionParam(); + + credentialsHelper.saveCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH, TEST_ENCRYPTED_PWD); + + ArgumentCaptor encryptionValueArgCaptor = ArgumentCaptor.forClass(Integer.class); + + Mockito.verify(allSharedPreferences, Mockito.times(1)).setDBEncryptionVersion(encryptionValueArgCaptor.capture()); + + Assert.assertEquals((Integer) BuildConfig.DB_ENCRYPTION_VERSION, encryptionValueArgCaptor.getValue()); + } + + @Test + public void generateLocalAuthCredentials() throws Exception { + + PowerMockito.mockStatic(SecurityHelper.class); + PowerMockito.when(SecurityHelper.getPasswordHash(TEST_DUMMY_PASSWORD)).thenReturn(null); + + credentialsHelper.generateLocalAuthCredentials(TEST_DUMMY_PASSWORD); + + PowerMockito.verifyStatic(SecurityHelper.class); + SecurityHelper.getPasswordHash(TEST_DUMMY_PASSWORD); + } + + @Test + public void testGenerateDBCredentialsReturnsCorrectBytesForSyncByProvider() { + + Mockito.doReturn(SyncFilter.PROVIDER).when(syncConfiguration).getEncryptionParam(); + + byte[] bytes = credentialsHelper.generateDBCredentials(TEST_DUMMY_PASSWORD, userInfo); + Assert.assertTrue(Arrays.equals(SecurityHelper.toBytes(TEST_DUMMY_PASSWORD), bytes)); + } + + @Test + public void testGenerateDBCredentialsReturnsCorrectBytesForSyncByTeam() { + + Mockito.doReturn(SyncFilter.TEAM_ID).when(syncConfiguration).getEncryptionParam(); + Mockito.doReturn(TEST_TEAM_ID).when(userService).getUserDefaultTeamId(userInfo); + + byte[] bytes = credentialsHelper.generateDBCredentials(TEST_DUMMY_PASSWORD, userInfo); + Assert.assertTrue(Arrays.equals(SecurityHelper.toBytes(TEST_TEAM_ID.toCharArray()), bytes)); + } + + @Test + public void testGenerateDBCredentialsReturnsCorrectBytesForSyncByLocation() { + + Mockito.doReturn(SyncFilter.LOCATION_ID).when(syncConfiguration).getEncryptionParam(); + Mockito.doReturn(TEST_LOCATION_ID).when(userService).getUserLocationId(userInfo); + + byte[] bytes = credentialsHelper.generateDBCredentials(TEST_DUMMY_PASSWORD, userInfo); + Assert.assertTrue(Arrays.equals(SecurityHelper.toBytes(TEST_LOCATION_ID.toCharArray()), bytes)); + } +} \ No newline at end of file From 1f58d9f74f653432d4b29b4adea6deaa5545b871 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Wed, 22 Jul 2020 22:19:49 +0300 Subject: [PATCH 61/70] Codacy clean up :recycle: --- .../src/main/java/org/smartregister/service/UserService.java | 2 -- .../main/java/org/smartregister/util/CredentialsHelper.java | 4 ++-- opensrp-app/src/main/java/org/smartregister/util/Utils.java | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index ef1cf5a15..6faa00e3b 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -218,8 +218,6 @@ public boolean isUserInValidGroup(final String userName, final char[] password) // Check if everything OK for local login if (keyStore != null && userName != null && password != null && !allSharedPreferences.fetchForceRemoteLogin(userName)) { - String username = userName.equalsIgnoreCase(allSharedPreferences.fetchRegisteredANM()) ? allSharedPreferences.fetchRegisteredANM() : userName; - byte[] storedHash = null; byte[] passwordHash = null; byte[] passwordSalt = null; diff --git a/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java b/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java index a9573d902..f680a999e 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java @@ -57,10 +57,10 @@ public void saveCredentials(String type, String encryptedPassphrase) { allSharedPreferences.savePassphrase(encryptedPassphrase, CoreLibrary.getInstance().getSyncConfiguration().getEncryptionParam().name()); allSharedPreferences.setDBEncryptionVersion(BuildConfig.DB_ENCRYPTION_VERSION); - } else if (CREDENTIALS_TYPE.LOCAL_AUTH.equals(type)) { + }/* else if (CREDENTIALS_TYPE.LOCAL_AUTH.equals(type)) { //saved in Account Manager by caller - } + }*/ } diff --git a/opensrp-app/src/main/java/org/smartregister/util/Utils.java b/opensrp-app/src/main/java/org/smartregister/util/Utils.java index 5cbcdf62b..372ecf861 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/Utils.java +++ b/opensrp-app/src/main/java/org/smartregister/util/Utils.java @@ -99,7 +99,6 @@ import timber.log.Timber; import static android.content.Context.INPUT_METHOD_SERVICE; -import static org.smartregister.AllConstants.ACCOUNT_DISABLED; import static org.smartregister.util.Log.logError; From 065a4731d04bea1aedd5896c0df04e421ccaf897 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Thu, 23 Jul 2020 08:13:07 +0300 Subject: [PATCH 62/70] Fix failing build :white_check_mark: - Fix tests --- gradle.properties | 1 - .../smartregister/service/UserService.java | 38 ++++++++++--- .../view/activity/mock/LoginActivityMock.java | 54 +++++++++++++++++++ .../security/SecurityHelperTest.java | 3 +- .../service/UserServiceTest.java | 43 +++++++-------- .../sync/intent/SyncIntentServiceTest.java | 6 ++- .../util/CredentialsHelperTest.java | 1 + .../view/activity/DrishtiApplicationTest.java | 14 ++--- .../ConnectivityChangeReceiverTest.java | 2 +- 9 files changed, 120 insertions(+), 42 deletions(-) create mode 100644 opensrp-app/src/main/java/org/smartregister/view/activity/mock/LoginActivityMock.java diff --git a/gradle.properties b/gradle.properties index 7c839ee8f..4b61efccd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,4 @@ VERSION_NAME=2.0.4-SNAPSHOT ->>>>>>> Refactor authentication logic VERSION_CODE=1 GROUP=org.smartregister POM_SETTING_DESCRIPTION=OpenSRP Client Core Application diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 6faa00e3b..b016d8864 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -220,25 +220,22 @@ public boolean isUserInValidGroup(final String userName, final char[] password) byte[] storedHash = null; byte[] passwordHash = null; - byte[] passwordSalt = null; try { // Compare stored password hash with provided password hash - storedHash = DrishtiApplication.getInstance().credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.LOCAL_AUTH); + storedHash = getLocalAuthenticationCredentials(); - passwordSalt = SecurityHelper.nullSafeBase64Decode(AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD_SALT, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); - passwordHash = SecurityHelper.hashPassword(password, passwordSalt); + passwordHash = generatePasswordHash(userName, password); if (storedHash != null && Arrays.equals(storedHash, passwordHash)) { - return isValidDBPassword(DrishtiApplication.getInstance().credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH)); + return isValidDBPassword(getDBAuthenticationCredentials()); } } catch (Exception e) { Timber.e(e); } finally { SecurityHelper.clearArray(password); SecurityHelper.clearArray(passwordHash); - SecurityHelper.clearArray(passwordSalt); SecurityHelper.clearArray(storedHash); } } @@ -246,6 +243,33 @@ public boolean isUserInValidGroup(final String userName, final char[] password) return false; } + @VisibleForTesting + protected byte[] generatePasswordHash(String userName, char[] password) { + byte[] passwordSalt = null; + try { + passwordSalt = SecurityHelper.nullSafeBase64Decode(AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD_SALT, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType())); + return SecurityHelper.hashPassword(password, passwordSalt); + } catch (Exception e) { + Timber.e(e); + } finally { + SecurityHelper.clearArray(passwordSalt); + } + + return null; + } + + + @VisibleForTesting + protected byte[] getLocalAuthenticationCredentials() { + return DrishtiApplication.getInstance().credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.LOCAL_AUTH); + } + + @VisibleForTesting + protected byte[] getDBAuthenticationCredentials() { + return DrishtiApplication.getInstance().credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); + } + + private boolean isValidDBPassword(byte[] password) { return DrishtiApplication.getInstance().getRepository().canUseThisPassword(password); } @@ -707,7 +731,7 @@ private KeyStore.PrivateKeyEntry createUserKeyPair(final String username) throws * @throws Exception */ @VisibleForTesting - protected byte[] decryptString(KeyStore.PrivateKeyEntry privateKeyEntry, String cipherText)throws Exception { + protected byte[] decryptString(KeyStore.PrivateKeyEntry privateKeyEntry, String cipherText) throws Exception { Cipher output; if (Build.VERSION.SDK_INT >= 23) { diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/mock/LoginActivityMock.java b/opensrp-app/src/main/java/org/smartregister/view/activity/mock/LoginActivityMock.java new file mode 100644 index 000000000..3a06a74be --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/mock/LoginActivityMock.java @@ -0,0 +1,54 @@ +package org.smartregister.view.activity.mock; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +import org.smartregister.R; +import org.smartregister.view.activity.BaseLoginActivity; + +/** + * Created by Raihan Ahmed on 11/11/17. + */ + +public class LoginActivityMock extends BaseLoginActivity { + + public static InputMethodManager inputManager; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + setTheme(R.style.AppTheme); //we need this here + super.onCreate(savedInstanceState); + } + + @Override + protected int getContentView() { + return 0; + } + + @Override + protected void initializePresenter() { + //Override + } + + @Override + public Object getSystemService(String name) { + if (name.equalsIgnoreCase(INPUT_METHOD_SERVICE)) { + return inputManager; + } else { + return super.getSystemService(name); + } + } + + @Nullable + @Override + public View getCurrentFocus() { + return findViewById(R.id.login_userNameText); + } + + @Override + public void goToHome(boolean isRemote) { + //Override + } +} \ No newline at end of file diff --git a/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java b/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java index bd6264b7e..d367a921e 100644 --- a/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java @@ -17,7 +17,6 @@ import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import org.smartregister.BaseUnitTest; import java.io.UnsupportedEncodingException; import java.nio.charset.CharacterCodingException; @@ -34,7 +33,7 @@ @RunWith(PowerMockRunner.class) @PrepareForTest({Base64.class, SecretKeyFactory.class}) -public class SecurityHelperTest extends BaseUnitTest { +public class SecurityHelperTest { @Mock private Editable editable; diff --git a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java index 98b9f6786..2ea30358a 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java @@ -1,6 +1,5 @@ package org.smartregister.service; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -20,8 +19,6 @@ import org.smartregister.domain.jsonmapping.LoginResponseData; import org.smartregister.domain.jsonmapping.Time; import org.smartregister.domain.jsonmapping.User; -import org.smartregister.repository.AllAlerts; -import org.smartregister.repository.AllEligibleCouples; import org.smartregister.repository.AllSettings; import org.smartregister.repository.AllSharedPreferences; import org.smartregister.repository.Repository; @@ -68,10 +65,6 @@ public class UserServiceTest extends BaseUnitTest { @Mock private AllSharedPreferences allSharedPreferences; @Mock - private AllAlerts allAlerts; - @Mock - private AllEligibleCouples allEligibleCouples; - @Mock private Session session; @Mock private HTTPAgent httpAgent; @@ -94,9 +87,9 @@ public class UserServiceTest extends BaseUnitTest { private LoginResponseData loginResponseData; - byte[] password = "Password Z".getBytes(); + private byte[] password = "Password Z".getBytes(); - private String user = "johndoe"; + private String userName = "johndoe"; @Mock private DrishtiApplication drishtiApplication; @@ -107,6 +100,7 @@ public void setUp() { Whitebox.setInternalState(DrishtiApplication.getInstance(), "repository", repository); when(configuration.getDrishtiApplication()).thenReturn(drishtiApplication); doReturn(repository).when(drishtiApplication).getRepository(); + when(allSharedPreferences.fetchRegisteredANM()).thenReturn(userName); userService = spy(new UserService(allSettings, allSharedPreferences, httpAgent, session, configuration, saveANMLocationTask, saveUserInfoTask, saveANMTeamTask)); userInfoJSON = "{\"locations\":{\"locationsHierarchy\":{\"map\":{\"cd4ed528-87cd-42ee-a175-5e7089521ebd\":{\"id\":\"cd4ed528-87cd-42ee-a175-5e7089521ebd\",\"label\":\"Pakistan\",\"node\":{\"locationId\":\"cd4ed528-87cd-42ee-a175-5e7089521ebd\",\"name\":\"Pakistan\",\"tags\":[\"Country\"],\"voided\":false},\"children\":{\"461f2be7-c95d-433c-b1d7-c68f272409d7\":{\"id\":\"461f2be7-c95d-433c-b1d7-c68f272409d7\",\"label\":\"Sindh\",\"node\":{\"locationId\":\"461f2be7-c95d-433c-b1d7-c68f272409d7\",\"name\":\"Sindh\",\"parentLocation\":{\"locationId\":\"cd4ed528-87cd-42ee-a175-5e7089521ebd\",\"name\":\"Pakistan\",\"voided\":false},\"tags\":[\"Province\"],\"voided\":false},\"children\":{\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\":{\"id\":\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\",\"label\":\"Karachi\",\"node\":{\"locationId\":\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\",\"name\":\"Karachi\",\"parentLocation\":{\"locationId\":\"461f2be7-c95d-433c-b1d7-c68f272409d7\",\"name\":\"Sindh\",\"parentLocation\":{\"locationId\":\"cd4ed528-87cd-42ee-a175-5e7089521ebd\",\"name\":\"Pakistan\",\"voided\":false},\"voided\":false},\"tags\":[\"City\"],\"voided\":false},\"children\":{\"60c21502-fec1-40f5-b77d-6df3f92771ce\":{\"id\":\"60c21502-fec1-40f5-b77d-6df3f92771ce\",\"label\":\"Baldia\",\"node\":{\"locationId\":\"60c21502-fec1-40f5-b77d-6df3f92771ce\",\"name\":\"Baldia\",\"parentLocation\":{\"locationId\":\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\",\"name\":\"Karachi\",\"parentLocation\":{\"locationId\":\"461f2be7-c95d-433c-b1d7-c68f272409d7\",\"name\":\"Sindh\",\"voided\":false},\"voided\":false},\"tags\":[\"Town\"],\"attributes\":{\"at1\":\"atttt1\"},\"voided\":false},\"parent\":\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\"}},\"parent\":\"461f2be7-c95d-433c-b1d7-c68f272409d7\"}},\"parent\":\"cd4ed528-87cd-42ee-a175-5e7089521ebd\"}}}},\"parentChildren\":{\"cd4ed528-87cd-42ee-a175-5e7089521ebd\":[\"461f2be7-c95d-433c-b1d7-c68f272409d7\"],\"461f2be7-c95d-433c-b1d7-c68f272409d7\":[\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\"],\"a529e2fc-6f0d-4e60-a5df-789fe17cca48\":[\"60c21502-fec1-40f5-b77d-6df3f92771ce\"]}}},\"user\":{\"username\":\"demotest\",\"roles\":[\"Provider\",\"Authenticated\",\"Thrive Member\"],\"permissions\":[\"Delete Reports\",\"Remove Allergies\",\"Edit Cohorts\",\"View Unpublished Forms\",\"Patient Dashboard - View Patient Summary\",\"Add Relationships\",\"Edit Problems\",\"Remove Problems\",\"Patient Dashboard - View Forms Section\",\"Manage Report Designs\",\"Add Patient Programs\",\"Delete Orders\",\"Manage Identifier Types\",\"Manage Person Attribute Types\",\"Add Patient Identifiers\",\"View Visit Types\",\"View Patients\",\"Delete Concept Proposals\",\"View Identifier Types\",\"Delete Encounters\",\"View Global Properties\",\"Edit Visits\",\"View Concept Map Types\",\"Add Users\",\"Delete Cohorts\",\"Manage Scheduled Report Tasks\",\"View Concepts\",\"Edit Concept Proposals\",\"Add Visits\",\"Edit Patient Programs\",\"Manage Concept Datatypes\",\"Manage Indicator Definitions\",\"View Concept Proposals\",\"Add Allergies\",\"Edit Allergies\",\"Delete Observations\",\"View Roles\",\"Manage Address Templates\",\"Configure Visits\",\"Manage Data Set Definitions\",\"View Concept Sources\",\"Patient Dashboard - View Regimen Section\",\"View Calculations\",\"Manage Encounter Roles\",\"Delete People\",\"Edit Report Objects\",\"View People\",\"Manage Concept Sources\",\"View Orders\",\"Manage Concept Map Types\",\"Delete Patient Programs\",\"Add Problems\",\"Edit People\",\"Manage Locations\",\"View Patient Programs\",\"View Field Types\",\"View Relationship Types\",\"Manage Visit Attribute Types\",\"Manage Order Types\",\"Manage TeamLocation Attribute Types\",\"Form Entry\",\"View Encounter Types\",\"View Encounter Roles\",\"Manage Programs\",\"Edit Reports\",\"View TeamLocation Attribute Types\",\"View Order Types\",\"Manage Relationship Types\",\"Manage Providers\",\"Manage Reports\",\"Manage Concept Classes\",\"Add Concept Proposals\",\"View Patient Identifiers\",\"View Privileges\",\"View Data Entry Statistics\",\"Patient Dashboard - View Graphs Section\",\"Manage Tokens\",\"Add Reports\",\"View Forms\",\"View Administration Functions\",\"Manage Relationships\",\"View Observations\",\"View Team\",\"Add Observations\",\"View Member\",\"View Report Objects\",\"Edit Relationships\",\"View Relationships\",\"Manage Scheduler\",\"View Allergies\",\"View Concept Reference Terms\",\"View Encounters\",\"Edit Patient Identifiers\",\"Edit Observations\",\"Delete Patients\",\"Delete Patient Identifiers\",\"View Person Attribute Types\",\"Add Encounters\",\"View Problems\",\"Manage FormEntry XSN\",\"View Visits\",\"Edit Team\",\"Manage Field Types\",\"Patient Dashboard - View Encounters Section\",\"Add Team\",\"Add Cohorts\",\"Add Patients\",\"Patient Dashboard - View Demographics Section\",\"Manage Concepts\",\"View Rule Definitions\",\"Add Orders\",\"Manage Visit Types\",\"Patient Dashboard - View Visits Section\",\"View Locations\",\"Manage Forms\",\"Edit Encounters\",\"Delete Relationships\",\"Manage Concept Reference Terms\",\"Add Report Objects\",\"Manage Alerts\",\"View Users\",\"Edit Patients\",\"Manage Concept Stop Words\",\"View Concept Classes\",\"View Patient Cohorts\",\"View Visit Attribute Types\",\"Manage TeamLocation Tags\",\"Manage Encounter Types\",\"View Concept Datatypes\",\"View Navigation Menu\",\"Delete Visits\",\"Add People\",\"Edit Orders\",\"Manage Concept Name tags\",\"Run Reports\",\"View Providers\",\"Patient Dashboard - View Overview Section\",\"Manage Cohort Definitions\",\"View Reports\",\"View Programs\",\"Delete Report Objects\",\"Manage Report Definitions\"],\"baseEntityId\":\"6637559e-ebf9-480a-9731-c47e16e95716\",\"preferredName\":\"Demo test User\",\"voided\":false},\"time\":{\"time\":\"2018-03-02 10:17:51\",\"timeZone\":\"Africa/Harare\"},\"team\":{\"identifier\":\"12345678\",\"person\":{\"gender\":\"F\",\"display\":\"MOH ZEIR Demo\",\"resourceVersion\":\"1.11\",\"dead\":false,\"uuid\":\"12481a02-9a78-4c45-9ead-ddf24d14b19d\",\"birthdateEstimated\":false,\"deathdateEstimated\":false,\"attributes\":[],\"voided\":false,\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/person/12481a02-9a78-4c45-9ead-ddf24d14b19d\"},{\"rel\":\"full\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/person/12481a02-9a78-4c45-9ead-ddf24d14b19d?v\\u003dfull\"}],\"preferredName\":{\"display\":\"MOH ZEIR Demo\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/person/12481a02-9a78-4c45-9ead-ddf24d14b19d/name/4ab4a8b9-3723-44a8-8733-815ee6d05ef7\"}],\"uuid\":\"4ab4a8b9-3723-44a8-8733-815ee6d05ef7\"}},\"teamMemberId\":1.0,\"patients\":[],\"resourceVersion\":\"1.8\",\"location\":[{\"parentLocation\":{\"display\":\"Fort Jameson\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/25089a50-0cf0-47e8-8bfe-fecabed92530\"}],\"uuid\":\"25089a50-0cf0-47e8-8bfe-fecabed92530\"},\"display\":\"Happy Kids Clinic\",\"resourceVersion\":\"1.9\",\"uuid\":\"42abc582-6658-488b-922e-7be500c070f3\",\"tags\":[{\"display\":\"Health Centre Urban\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/locationtag/86c5e41b-08f0-495d-9130-170556c05041\"}],\"uuid\":\"86c5e41b-08f0-495d-9130-170556c05041\"},{\"display\":\"Health Facility\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/locationtag/4d9fce9d-c83a-46aa-b1d9-121da6176758\"}],\"uuid\":\"4d9fce9d-c83a-46aa-b1d9-121da6176758\"}],\"name\":\"Happy Kids Clinic\",\"retired\":false,\"attributes\":[{\"display\":\"dhis_ou_id: k2SgIKwkSh1\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42abc582-6658-488b-922e-7be500c070f3/attribute/01ec1f7c-e061-4f37-9d2c-ce1c7fe99c36\"}],\"uuid\":\"01ec1f7c-e061-4f37-9d2c-ce1c7fe99c36\"}],\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42abc582-6658-488b-922e-7be500c070f3\"},{\"rel\":\"full\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42abc582-6658-488b-922e-7be500c070f3?v\\u003dfull\"}],\"childLocations\":[{\"display\":\"Happy Kids Clinic: Zone 1\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42b88545-7ebb-4e11-8d1a-3d3a924c8af4\"}],\"uuid\":\"42b88545-7ebb-4e11-8d1a-3d3a924c8af4\"},{\"display\":\"Happy Kids Clinic: Zone 2\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/8a40cd7e-b8d4-4c6e-b88f-a77272fec630\"}],\"uuid\":\"8a40cd7e-b8d4-4c6e-b88f-a77272fec630\"},{\"display\":\"Happy Kids Clinic: Zone 3\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/5e79ae00-5a69-4814-aace-30e4717f823a\"}],\"uuid\":\"5e79ae00-5a69-4814-aace-30e4717f823a\"},{\"display\":\"Happy Kids Clinic: Zone 4\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/e79ff5bc-b6ff-46bc-9bbf-0cedc7d6c4c7\"}],\"uuid\":\"e79ff5bc-b6ff-46bc-9bbf-0cedc7d6c4c7\"}]}],\"team\":{\"teamName\":\"Demo\",\"dateCreated\":\"2017-04-06T09:21:39.000+0200\",\"display\":\"Demo\",\"resourceVersion\":\"1.8\",\"location\":{\"parentLocation\":{\"display\":\"Fort Jameson\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/25089a50-0cf0-47e8-8bfe-fecabed92530\"}],\"uuid\":\"25089a50-0cf0-47e8-8bfe-fecabed92530\"},\"display\":\"Happy Kids Clinic\",\"resourceVersion\":\"1.9\",\"uuid\":\"42abc582-6658-488b-922e-7be500c070f3\",\"tags\":[{\"display\":\"Health Centre Urban\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/locationtag/86c5e41b-08f0-495d-9130-170556c05041\"}],\"uuid\":\"86c5e41b-08f0-495d-9130-170556c05041\"},{\"display\":\"Health Facility\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/locationtag/4d9fce9d-c83a-46aa-b1d9-121da6176758\"}],\"uuid\":\"4d9fce9d-c83a-46aa-b1d9-121da6176758\"}],\"name\":\"Happy Kids Clinic\",\"retired\":false,\"attributes\":[{\"display\":\"dhis_ou_id: k2SgIKwkSh1\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42abc582-6658-488b-922e-7be500c070f3/attribute/01ec1f7c-e061-4f37-9d2c-ce1c7fe99c36\"}],\"uuid\":\"01ec1f7c-e061-4f37-9d2c-ce1c7fe99c36\"}],\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42abc582-6658-488b-922e-7be500c070f3\"},{\"rel\":\"full\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42abc582-6658-488b-922e-7be500c070f3?v\\u003dfull\"}],\"childLocations\":[{\"display\":\"Happy Kids Clinic: Zone 1\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/42b88545-7ebb-4e11-8d1a-3d3a924c8af4\"}],\"uuid\":\"42b88545-7ebb-4e11-8d1a-3d3a924c8af4\"},{\"display\":\"Happy Kids Clinic: Zone 2\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/8a40cd7e-b8d4-4c6e-b88f-a77272fec630\"}],\"uuid\":\"8a40cd7e-b8d4-4c6e-b88f-a77272fec630\"},{\"display\":\"Happy Kids Clinic: Zone 3\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/5e79ae00-5a69-4814-aace-30e4717f823a\"}],\"uuid\":\"5e79ae00-5a69-4814-aace-30e4717f823a\"},{\"display\":\"Happy Kids Clinic: Zone 4\",\"links\":[{\"rel\":\"self\",\"uri\":\"http://openmrs.zeir-stage.smartregister.org/openmrs/ws/rest/v1/location/e79ff5bc-b6ff-46bc-9bbf-0cedc7d6c4c7\"}],\"uuid\":\"e79ff5bc-b6ff-46bc-9bbf-0cedc7d6c4c7\"}]},\"teamIdentifier\":\"Demo\",\"uuid\":\"7bfb4bb3-2689-404c-a5d4-f5cbe1aea9c4\"},\"isTeamLead\":true,\"uuid\":\"6ea953fb-46a2-4415-ae53-299ce909894b\"}}"; loginResponseData = AssetHandler.jsonStringToJava(userInfoJSON, LoginResponseData.class); @@ -188,14 +182,15 @@ public void shouldRegisterANewUser() { userInfo.user = user; when(allSharedPreferences.fetchRegisteredANM()).thenReturn("user Z"); - ArgumentCaptor usernameCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor usernameCaptor = ArgumentCaptor.forClass(User.class); userService.processLoginResponseDataForUser(newUsername, userInfo); - verify(allSharedPreferences).updateANMUserName(usernameCaptor.capture()); - String value = usernameCaptor.getValue(); - Assert.assertNotNull(value); - Assert.assertEquals(newUsername, value); + verify(userService).saveUserInfo(usernameCaptor.capture()); + User value = usernameCaptor.getValue(); + assertNotNull(value); + assertNotNull(value.getUsername()); + assertEquals(newUsername, value.getUsername()); } @@ -301,16 +296,18 @@ public void testValidateDeviceTimeSameTimeTimeAndTimeZone() { public void testValidateStoredServerTimeZoneForNullServerTimeZoneReturnsError() { when(allSharedPreferences.fetchServerTimeZone()).thenReturn(null); assertEquals(TimeStatus.ERROR, userService.validateStoredServerTimeZone()); - verify(allSharedPreferences).saveForceRemoteLogin(true, user); + verify(allSharedPreferences).saveForceRemoteLogin(true, userName); } @Test public void testValidateStoredServerTimeZoneForDifferentTimeZoneServerTimeZoneReturnsMismatch() { when(allSharedPreferences.fetchServerTimeZone()).thenReturn("Africa/Nairobi"); + when(allSharedPreferences.fetchRegisteredANM()).thenReturn(userName); + TimeZone.setDefault(TimeZone.getTimeZone("GMT")); assertEquals(TimeStatus.TIMEZONE_MISMATCH, userService.validateStoredServerTimeZone()); - verify(allSharedPreferences).saveForceRemoteLogin(true, user); + verify(allSharedPreferences).saveForceRemoteLogin(true, userName); } @@ -319,7 +316,7 @@ public void testValidateStoredServerTimeZoneForSameTimeTimeAndTimeZone() { when(allSharedPreferences.fetchServerTimeZone()).thenReturn("Africa/Nairobi"); TimeZone.setDefault(TimeZone.getTimeZone("Africa/Nairobi")); assertEquals(TimeStatus.OK, userService.validateStoredServerTimeZone()); - verify(allSharedPreferences, never()).saveForceRemoteLogin(true, user); + verify(allSharedPreferences, never()).saveForceRemoteLogin(true, userName); } @@ -333,13 +330,18 @@ public void testIsUserInValidGroupForValidUserAndPassword() throws Exception { Whitebox.setInternalState(userService, "keyStore", keyStore); Whitebox.setInternalState(keyStore, "initialized", true); Whitebox.setInternalState(keyStore, "keyStoreSpi", keyStoreSpi); - when(keyStore.containsAlias(user)).thenReturn(true); + when(keyStore.containsAlias(userName)).thenReturn(true); KeyStore.PrivateKeyEntry privateKeyEntry = PowerMockito.mock(KeyStore.PrivateKeyEntry.class); - when(keyStore.getEntry(user, null)).thenReturn(privateKeyEntry); + when(keyStore.getEntry(userName, null)).thenReturn(privateKeyEntry); userService = spy(userService); doReturn(password).when(userService).decryptString(privateKeyEntry, "RandomSECURE_TEXT"); when(repository.canUseThisPassword(password)).thenReturn(true); - assertTrue(userService.isUserInValidGroup(user, SecurityHelper.toChars(password))); + + doReturn(password).when(userService).generatePasswordHash(userName, SecurityHelper.toChars(password)); + doReturn(password).when(userService).getLocalAuthenticationCredentials(); + doReturn(password).when(userService).getDBAuthenticationCredentials(); + + assertTrue(userService.isUserInValidGroup(userName, SecurityHelper.toChars(password))); verify(repository).canUseThisPassword(password); } @@ -365,7 +367,6 @@ public void testIsUserInPioneerGroupShouldReturnTrueForPioneerUser() throws Exce Whitebox.setInternalState(userService, "keyStore", keyStore); Whitebox.setInternalState(keyStore, "initialized", true); Whitebox.setInternalState(keyStore, "keyStoreSpi", keyStoreSpi); - String password = UUID.randomUUID().toString(); String user = "johndoe"; when(keyStore.containsAlias(user)).thenReturn(true); KeyStore.PrivateKeyEntry privateKeyEntry = PowerMockito.mock(KeyStore.PrivateKeyEntry.class); diff --git a/opensrp-app/src/test/java/org/smartregister/sync/intent/SyncIntentServiceTest.java b/opensrp-app/src/test/java/org/smartregister/sync/intent/SyncIntentServiceTest.java index 3556d19d3..8b5686323 100644 --- a/opensrp-app/src/test/java/org/smartregister/sync/intent/SyncIntentServiceTest.java +++ b/opensrp-app/src/test/java/org/smartregister/sync/intent/SyncIntentServiceTest.java @@ -1,5 +1,7 @@ package org.smartregister.sync.intent; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -19,6 +21,8 @@ import org.smartregister.receiver.SyncStatusBroadcastReceiver; import org.smartregister.util.SyncUtils; +import java.io.IOException; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.spy; @@ -76,7 +80,7 @@ public void testHandleSyncSendFetchStartedBroadCast() throws PackageManager.Name } @Test - public void testHandleSyncCallsLogoutUserIfHasValidAuthorizationIsFalse() { + public void testHandleSyncCallsLogoutUserIfHasValidAuthorizationIsFalse() throws AuthenticatorException, OperationCanceledException, IOException { Whitebox.setInternalState(syncIntentService, "syncUtils", syncUtils); when(syncUtils.verifyAuthorization()).thenReturn(false); when(syncConfiguration.disableSyncToServerIfUserIsDisabled()).thenReturn(true); diff --git a/opensrp-app/src/test/java/org/smartregister/util/CredentialsHelperTest.java b/opensrp-app/src/test/java/org/smartregister/util/CredentialsHelperTest.java index 1d53a3f0f..b8b2376bf 100644 --- a/opensrp-app/src/test/java/org/smartregister/util/CredentialsHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/util/CredentialsHelperTest.java @@ -149,6 +149,7 @@ public void testSaveCredentialsUpdatesSharedPreferencesWithNewDBEncryptionVersio @Test public void generateLocalAuthCredentials() throws Exception { + Assert.assertNotNull(credentialsHelper); PowerMockito.mockStatic(SecurityHelper.class); PowerMockito.when(SecurityHelper.getPasswordHash(TEST_DUMMY_PASSWORD)).thenReturn(null); diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java index 557b269d8..9357c9ea2 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java @@ -16,13 +16,12 @@ import org.smartregister.Context; import org.smartregister.CoreLibrary; import org.smartregister.customshadows.FontTextViewShadow; -import org.smartregister.repository.AllSharedPreferences; import org.smartregister.repository.Repository; -import org.smartregister.service.UserService; import org.smartregister.shadows.ShadowAppDatabase; import org.smartregister.shadows.ShadowDrawableResourcesImpl; import org.smartregister.shadows.ShadowJobManager; import org.smartregister.shadows.ShadowSQLiteDatabase; +import org.smartregister.util.CredentialsHelper; /** * Created by Ephraim Kigamba - nek.eam@gmail.com on 30-06-2020. @@ -70,18 +69,15 @@ public void getRepository() { @Test public void getPassword() { - String username = "anm"; byte[] password = "pwd".getBytes(); drishtiApplication.onCreate(); Assert.assertNull(ReflectionHelpers.getField(drishtiApplication, "password")); - UserService userService = Mockito.spy(drishtiApplication.getContext().userService()); - ReflectionHelpers.setField(drishtiApplication.getContext(), "userService", userService); - AllSharedPreferences allSharedPreferences = Mockito.spy(drishtiApplication.getContext().userService().getAllSharedPreferences()); - ReflectionHelpers.setField(drishtiApplication.getContext().userService(), "allSharedPreferences", allSharedPreferences); - Mockito.doReturn(username).when(allSharedPreferences).fetchRegisteredANM(); - Mockito.doReturn(password).when(userService).getDecryptedPassphraseValue(Mockito.eq(username)); + CredentialsHelper credentialsProvider = Mockito.spy(new CredentialsHelper(Mockito.mock(Context.class))); + Mockito.doReturn(password).when(credentialsProvider).getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); + + ReflectionHelpers.setField(drishtiApplication, "credentialsHelper", credentialsProvider); Assert.assertEquals(password, drishtiApplication.getPassword()); } diff --git a/opensrp-app/src/test/java/org/smartregister/view/receiver/ConnectivityChangeReceiverTest.java b/opensrp-app/src/test/java/org/smartregister/view/receiver/ConnectivityChangeReceiverTest.java index e9c482303..164564fd0 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/receiver/ConnectivityChangeReceiverTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/receiver/ConnectivityChangeReceiverTest.java @@ -34,7 +34,7 @@ public void setUp() throws Exception { // Make sure the user is logged in Session session = ReflectionHelpers.getField(CoreLibrary.getInstance().context().userService(), "session"); - session.setPassword(""); + session.setPassword("".getBytes()); session.start(360 * 60 * 1000); } From d2159548420f3f49b48071ebd76e7532c239a111 Mon Sep 17 00:00:00 2001 From: Simon Njoroge Date: Fri, 14 Aug 2020 21:06:49 +0300 Subject: [PATCH 63/70] Add support for downloading all locations --- .../job/SyncAllLocationsServiceJob.java | 20 ++++++++ .../sync/helper/LocationServiceHelper.java | 46 ++++++++++++++++++- .../intent/SyncAllLocationsIntentService.java | 39 ++++++++++++++++ .../job/SyncAllLocationsServiceJobTest.java | 46 +++++++++++++++++++ .../helper/LocationServiceHelperTest.java | 21 +++++++-- 5 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 opensrp-app/src/main/java/org/smartregister/job/SyncAllLocationsServiceJob.java create mode 100644 opensrp-app/src/main/java/org/smartregister/sync/intent/SyncAllLocationsIntentService.java create mode 100644 opensrp-app/src/test/java/org/smartregister/job/SyncAllLocationsServiceJobTest.java diff --git a/opensrp-app/src/main/java/org/smartregister/job/SyncAllLocationsServiceJob.java b/opensrp-app/src/main/java/org/smartregister/job/SyncAllLocationsServiceJob.java new file mode 100644 index 000000000..888100758 --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/job/SyncAllLocationsServiceJob.java @@ -0,0 +1,20 @@ +package org.smartregister.job; + +import android.content.Intent; +import android.support.annotation.NonNull; + +import org.smartregister.AllConstants; +import org.smartregister.sync.intent.SyncAllLocationsIntentService; + +public class SyncAllLocationsServiceJob extends BaseJob { + + public static final String TAG = "SyncAllLocationsServiceJob"; + + @NonNull + @Override + protected Result onRunJob(@NonNull Params params) { + Intent intent = new Intent(getApplicationContext(), SyncAllLocationsIntentService.class); + getApplicationContext().startService(intent); + return params != null && params.getExtras().getBoolean(AllConstants.INTENT_KEY.TO_RESCHEDULE, false) ? Result.RESCHEDULE : Result.SUCCESS; + } +} diff --git a/opensrp-app/src/main/java/org/smartregister/sync/helper/LocationServiceHelper.java b/opensrp-app/src/main/java/org/smartregister/sync/helper/LocationServiceHelper.java index 24d9e6f3a..2a16e093a 100644 --- a/opensrp-app/src/main/java/org/smartregister/sync/helper/LocationServiceHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/sync/helper/LocationServiceHelper.java @@ -67,7 +67,7 @@ public class LocationServiceHelper extends BaseHelper { private LocationTagRepository locationTagRepository; private StructureRepository structureRepository; private long totalRecords; - private SyncProgress syncProgress; + private SyncProgress syncProgress; public LocationServiceHelper(LocationRepository locationRepository, LocationTagRepository locationTagRepository, StructureRepository structureRepository) { this.context = CoreLibrary.getInstance().context().applicationContext(); @@ -135,7 +135,7 @@ private List batchSyncLocationsStructures(boolean isJurisdiction, List locations.addAll(batchLocationStructures); syncProgress.setPercentageSynced(Utils.calculatePercentage(totalRecords, locations.size())); sendSyncProgressBroadcast(syncProgress, context); - return batchSyncLocationsStructures(isJurisdiction, locations, false); + return batchSyncLocationsStructures(isJurisdiction, locations, false); } } catch (Exception e) { @@ -316,6 +316,7 @@ public void syncUpdatedLocationsToServer() { } } } + public void fetchOpenMrsLocationsByTeamIds() throws NoHttpResponseException, JSONException { HTTPAgent httpAgent = getHttpAgent(); if (httpAgent == null) { @@ -380,5 +381,46 @@ public String getFormattedBaseUrl() { return baseUrl; } + public void fetchAllLocations() { + try { + HTTPAgent httpAgent = getHttpAgent(); + if (httpAgent == null) { + throw new IllegalArgumentException(LOCATION_STRUCTURE_URL + " http agent is null"); + } + + String baseUrl = getFormattedBaseUrl(); + + JSONObject request = new JSONObject(); + request.put("is_jurisdiction", true); + request.put("serverVersion", 0); + + Response resp = httpAgent.post( + MessageFormat.format("{0}{1}", baseUrl, LOCATION_STRUCTURE_URL), + request.toString()); + + if (resp.isFailure()) { + throw new NoHttpResponseException(LOCATION_STRUCTURE_URL + " not returned data"); + } + + List locations = locationGson.fromJson( + resp.payload().toString(), + new TypeToken>() { + }.getType() + ); + + + for (Location location : locations) { + try { + location.setSyncStatus(BaseRepository.TYPE_Synced); + + locationRepository.addOrUpdate(location); + } catch (Exception e) { + Timber.e(e, "EXCEPTION %s", e.toString()); + } + } + } catch (Exception e) { + Timber.e(e, "EXCEPTION %s", e.toString()); + } + } } diff --git a/opensrp-app/src/main/java/org/smartregister/sync/intent/SyncAllLocationsIntentService.java b/opensrp-app/src/main/java/org/smartregister/sync/intent/SyncAllLocationsIntentService.java new file mode 100644 index 000000000..2a18d0123 --- /dev/null +++ b/opensrp-app/src/main/java/org/smartregister/sync/intent/SyncAllLocationsIntentService.java @@ -0,0 +1,39 @@ +package org.smartregister.sync.intent; + +import android.content.Intent; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.joda.time.DateTime; +import org.smartregister.domain.LocationProperty; +import org.smartregister.sync.helper.LocationServiceHelper; +import org.smartregister.util.DateTimeTypeConverter; +import org.smartregister.util.PropertiesConverter; + +import timber.log.Timber; + +public class SyncAllLocationsIntentService extends BaseSyncIntentService { + private static final String TAG = "SyncAllLocationsIntentService"; + + public static Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + .registerTypeAdapter(DateTime.class, new DateTimeTypeConverter()) + .registerTypeAdapter(LocationProperty.class, new PropertiesConverter()).create(); + + public SyncAllLocationsIntentService() { + super(TAG); + } + + @Override + protected void onHandleIntent(Intent intent) { + super.onHandleIntent(intent); + + LocationServiceHelper locationServiceHelper = LocationServiceHelper.getInstance(); + + try { + locationServiceHelper.fetchAllLocations(); + } catch (Exception e) { + Timber.e(e); + } + } +} diff --git a/opensrp-app/src/test/java/org/smartregister/job/SyncAllLocationsServiceJobTest.java b/opensrp-app/src/test/java/org/smartregister/job/SyncAllLocationsServiceJobTest.java new file mode 100644 index 000000000..b86cdee00 --- /dev/null +++ b/opensrp-app/src/test/java/org/smartregister/job/SyncAllLocationsServiceJobTest.java @@ -0,0 +1,46 @@ +package org.smartregister.job; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.smartregister.BaseUnitTest; + +import static junit.framework.Assert.assertEquals; + +public class SyncAllLocationsServiceJobTest extends BaseUnitTest { + + @Mock + private Context context; + + @Mock + private ComponentName componentName; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testOnRunJobStartsCorrectService() { + SyncAllLocationsServiceJob syncAllLocationsServiceJob = new SyncAllLocationsServiceJob(); + SyncAllLocationsServiceJob spy = Mockito.spy(syncAllLocationsServiceJob); + + ArgumentCaptor intent = ArgumentCaptor.forClass(Intent.class); + + Mockito.doReturn(context).when(spy).getApplicationContext(); + Mockito.doReturn(componentName).when(context).startService(ArgumentMatchers.any(Intent.class)); + + spy.onRunJob(null); + Mockito.verify(context).startService(intent.capture()); + + assertEquals("org.smartregister.sync.intent.SyncAllLocationsIntentService", intent.getValue().getComponent().getClassName()); + } +} diff --git a/opensrp-app/src/test/java/org/smartregister/sync/helper/LocationServiceHelperTest.java b/opensrp-app/src/test/java/org/smartregister/sync/helper/LocationServiceHelperTest.java index 43f414286..73d5b93bf 100644 --- a/opensrp-app/src/test/java/org/smartregister/sync/helper/LocationServiceHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/sync/helper/LocationServiceHelperTest.java @@ -100,7 +100,6 @@ public void setUp() { @Test public void fetchOpenMrsLocationsByTeamIds() throws JSONException, NoHttpResponseException { - Mockito.doReturn(new Response<>(ResponseStatus.success, "[{\"locations\":[{\"display\":\"Tabata Dampo - Unified\",\"uuid\":\"fb7ed5db-138d-4e6f-94d8-bc443b58dadb\"}]," + "\"team\":{\"location\":{\"display\":\"Madona - Unified\",\"uuid\":\"bcf5a36d-fb53-4de9-9813-01f1d480e3fe\"}}}]")) @@ -171,7 +170,7 @@ public void testSyncLocations() { assertFalse(expectedLocation.getGeometry() == null); Mockito.doReturn(new Response<>(ResponseStatus.success, // returned on first call - LocationServiceHelper.locationGson.toJson(locations)).withTotalRecords(1l), + LocationServiceHelper.locationGson.toJson(locations)).withTotalRecords(1l), new Response<>(ResponseStatus.success, //returned on second call LocationServiceHelper.locationGson.toJson(new ArrayList<>())).withTotalRecords(0l)) @@ -216,7 +215,6 @@ public void testFetchLocationsStructures() { verify(locationServiceHelper).syncLocationsStructures(false); verify(locationServiceHelper).syncCreatedStructureToServer(); verify(locationServiceHelper).syncCreatedStructureToServer(); - } @Test @@ -238,7 +236,6 @@ public void testSyncCreatedStructureToServer() { String requestString = stringArgumentCaptor.getAllValues().get(1); assertEquals(LocationServiceHelper.locationGson.toJson(structures), requestString); verify(structureRepository).markStructuresAsSynced(expectedStructure.getId()); - } @Test @@ -260,7 +257,21 @@ public void testSyncUpdatedLocationsToServer() { String requestString = stringArgumentCaptor.getAllValues().get(1); assertEquals(LocationServiceHelper.locationGson.toJson(locations), requestString); verify(locationRepository).markLocationsAsSynced(expectedLocation.getId()); - } + @Test + public void testFetchAllLocations() { + Mockito.doReturn(new Response<>(ResponseStatus.success, + "[{\"type\":\"Feature\",\"id\":\"c2aa34c2-789b-467e-a0ab-9ea3d61632cf\",\"properties\":{\"status\":\"Active\",\"parentId\":\"\",\"name\":\"Level1\",\"geographicLevel\":1,\"version\":0},\"serverVersion\":0,\"locationTags\":[{\"id\":1,\"active\":true,\"name\":\"County\",\"description\":\"County Location Tag\"}]}," + + "{\"type\":\"Feature\",\"id\":\"9dee11ba-d352-4df5-9efa-4607723b316e\",\"properties\":{\"status\":\"Active\",\"parentId\":\"c2aa34c2-789b-467e-a0ab-9ea3d61632cf\",\"name\":\"Level2\",\"geographicLevel\":2,\"version\":0},\"serverVersion\":0,\"locationTags\":[{\"id\":2,\"active\":true,\"name\":\"Subcounty\",\"description\":\"Subcounty Location Tag\"}]}]")) + .when(httpAgent).post(stringArgumentCaptor.capture(), stringArgumentCaptor.capture()); + + locationServiceHelper.fetchAllLocations(); + Mockito.verify(locationRepository, Mockito.atLeastOnce()).addOrUpdate(Mockito.any(Location.class)); + + String syncUrl = stringArgumentCaptor.getAllValues().get(0); + assertEquals("https://sample-stage.smartregister.org/opensrp//rest/location/sync", syncUrl); + String requestString = stringArgumentCaptor.getAllValues().get(1); + assertEquals("{\"is_jurisdiction\":true,\"serverVersion\":0}", requestString); + } } \ No newline at end of file From 92b6ad6c15bccc1786f91d0f80361963ee32a05d Mon Sep 17 00:00:00 2001 From: Simon Njoroge Date: Fri, 14 Aug 2020 21:07:11 +0300 Subject: [PATCH 64/70] Bump version up --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4b61efccd..b98ca1aa7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=2.0.4-SNAPSHOT +VERSION_NAME=2.0.5-SNAPSHOT VERSION_CODE=1 GROUP=org.smartregister POM_SETTING_DESCRIPTION=OpenSRP Client Core Application From 38beb07cef3ed740d1b7b51d61cfd03ae15bad87 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Wed, 19 Aug 2020 11:52:48 +0300 Subject: [PATCH 65/70] Fix build :green_heart: --- .../org/smartregister/view/activity/DrishtiApplication.java | 2 +- .../src/test/java/org/smartregister/TestApplication.java | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java index f1752b42e..46c7adf60 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java @@ -128,7 +128,7 @@ public CredentialsHelper credentialsProvider() { return credentialsHelper; } - public byte[] getPassword() { + public final byte[] getPassword() { if (password == null) { password = credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); diff --git a/opensrp-app/src/test/java/org/smartregister/TestApplication.java b/opensrp-app/src/test/java/org/smartregister/TestApplication.java index 24cd7efb2..301cb5188 100644 --- a/opensrp-app/src/test/java/org/smartregister/TestApplication.java +++ b/opensrp-app/src/test/java/org/smartregister/TestApplication.java @@ -7,7 +7,6 @@ import org.smartregister.sync.P2PClassifier; import org.smartregister.view.activity.DrishtiApplication; - import static org.mockito.Mockito.mock; /** @@ -47,11 +46,6 @@ public void setContext(Context context) { this.context = context; } - @Override - public byte[] getPassword() { - return new byte[]; - } - @Nullable @Override public P2PClassifier getP2PClassifier() { From 695e2ee0d1f930c895b64b55f30ac40341141440 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 25 Aug 2020 15:44:54 +0300 Subject: [PATCH 66/70] Fix failing unit tests :white_check_mark: Code clean up --- opensrp-app/build.gradle | 15 +- .../login/task/RemoteLoginTask.java | 6 + .../FormAttributeParserTest.java | 4 +- .../interactor/BaseLoginInteractorTest.java | 258 ++++++++++++------ .../login/model/BaseLoginModelTest.java | 11 +- .../presenter/BaseLoginPresenterTest.java | 16 +- .../repository/RepositoryRobolectricTest.java | 3 +- .../repository/RepositoryTest.java | 28 +- .../service/UserServiceTest.java | 3 +- .../sync/intent/SyncIntentServiceTest.java | 2 +- .../view/activity/BaseLoginActivityTest.java | 6 + .../activity/BaseRegisterActivityTest.java | 9 +- .../mock/BaseRegisterActivityMock.java | 6 + .../ConnectivityChangeReceiverTest.java | 4 +- 14 files changed, 253 insertions(+), 118 deletions(-) diff --git a/opensrp-app/build.gradle b/opensrp-app/build.gradle index 2a61e3e87..e71204063 100644 --- a/opensrp-app/build.gradle +++ b/opensrp-app/build.gradle @@ -227,23 +227,10 @@ dependencies { implementation 'com.evernote:android-job:1.2.6' implementation group: 'commons-validator', name: 'commons-validator', version: '1.6' implementation 'de.hdodenhof:circleimageview:2.2.0' - - implementation('org.smartregister:android-p2p-sync:0.3.6-SNAPSHOT') { - exclude group: 'com.android.support', module: 'support-v4' - exclude group: 'com.android.support', module: 'appcompat-v7' - exclude group: 'android.arch.core', module: 'runtime' - } - - implementation 'org.smartregister:opensrp-client-utils:0.0.2-SNAPSHOT' - - implementation 'org.smartregister:opensrp-plan-evaluator:0.0.19-SNAPSHOT' - implementation 'xerces:xercesImpl:2.12.0' -} - -dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') + androidTestImplementation 'junit:junit:4.12' testImplementation group: 'com.google.android', name: 'android-test', version: '4.1.1.4' diff --git a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java index c1ac8883e..4f3b8064a 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java +++ b/opensrp-app/src/main/java/org/smartregister/login/task/RemoteLoginTask.java @@ -134,6 +134,12 @@ protected LoginResponse doInBackground(Void... params) { } } + } else { + if (response.getAccountError() != null && response.getAccountError().getError() != null) { + return LoginResponse.valueOf(response.getAccountError().getError()); + } else if (loginResponse == null) { + return null; + } } } else { diff --git a/opensrp-app/src/test/java/org/smartregister/clientandeventmodel/FormAttributeParserTest.java b/opensrp-app/src/test/java/org/smartregister/clientandeventmodel/FormAttributeParserTest.java index 826e7b9d0..9b0ab0c85 100644 --- a/opensrp-app/src/test/java/org/smartregister/clientandeventmodel/FormAttributeParserTest.java +++ b/opensrp-app/src/test/java/org/smartregister/clientandeventmodel/FormAttributeParserTest.java @@ -216,8 +216,8 @@ public void assertGetFieldnameSubForm() throws Exception { Mockito.when(context.getAssets()).thenReturn(assetManager); FileInputStream formDefinitionIS = (new FileInputStream(getFileFromPath(this, formDefinition))); String stringOfFormDefinition = getStringFromStream(formDefinitionIS); - JsonParser jsonparser = new JsonParser(); - Object obj = jsonparser.parse(stringOfFormDefinition); + + Object obj = JsonParser.parseString(stringOfFormDefinition); Mockito.doReturn(obj).when(spyparser).getFormDefinitionData(FORMNAME); Mockito.when(assetManager.open(model)).thenReturn(new FileInputStream(getFileFromPath(this, model))); Mockito.when(assetManager.open(formJSON)).thenReturn(new FileInputStream(getFileFromPath(this, formMultiJSON))); diff --git a/opensrp-app/src/test/java/org/smartregister/login/interactor/BaseLoginInteractorTest.java b/opensrp-app/src/test/java/org/smartregister/login/interactor/BaseLoginInteractorTest.java index e57dfb37f..cfaaff446 100644 --- a/opensrp-app/src/test/java/org/smartregister/login/interactor/BaseLoginInteractorTest.java +++ b/opensrp-app/src/test/java/org/smartregister/login/interactor/BaseLoginInteractorTest.java @@ -1,6 +1,9 @@ package org.smartregister.login.interactor; +import android.accounts.AccountManager; import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import org.json.JSONArray; @@ -10,6 +13,7 @@ import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -22,6 +26,11 @@ import org.smartregister.CoreLibrary; import org.smartregister.R; import org.smartregister.SyncConfiguration; +import org.smartregister.account.AccountAuthenticatorXml; +import org.smartregister.account.AccountConfiguration; +import org.smartregister.account.AccountError; +import org.smartregister.account.AccountHelper; +import org.smartregister.account.AccountResponse; import org.smartregister.domain.LoginResponse; import org.smartregister.domain.Setting; import org.smartregister.domain.TimeStatus; @@ -33,28 +42,30 @@ import org.smartregister.repository.AllSettings; import org.smartregister.repository.AllSharedPreferences; import org.smartregister.repository.UniqueIdRepository; +import org.smartregister.service.HTTPAgent; import org.smartregister.service.UserService; import org.smartregister.shadows.LoginInteractorShadow; import org.smartregister.shadows.ShadowNetworkUtils; import org.smartregister.view.contract.BaseLoginContract; import java.lang.ref.WeakReference; +import java.util.Arrays; import java.util.Date; +import java.util.LinkedList; import java.util.TimeZone; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.smartregister.domain.LoginResponse.NO_INTERNET_CONNECTIVITY; +import static org.smartregister.domain.LoginResponse.SUCCESS_WITH_EMPTY_RESPONSE; +import static org.smartregister.domain.LoginResponse.UNAUTHORIZED; +import static org.smartregister.domain.LoginResponse.UNKNOWN_RESPONSE; /** * Created by samuelgithengi on 8/4/20. @@ -77,6 +88,9 @@ public class BaseLoginInteractorTest extends BaseRobolectricUnitTest { @Mock private UserService userService; + @Mock + private Bundle userDataBundle; + @Mock private AllSharedPreferences allSharedPreferences; @@ -108,28 +122,75 @@ public class BaseLoginInteractorTest extends BaseRobolectricUnitTest { private LoginResponseData loginResponseData; - private String user = "johndoe"; - private String password = "qwerty"; + @Mock + private AccountConfiguration accountConfiguration; + + @Mock + private SharedPreferences sharedPreferences; + + @Mock + private HTTPAgent httpAgent; + + @Mock + private AccountManager mAccountManager; + + @Mock + private SharedPreferences.Editor sharePrefEditor; + + @Mock + private AccountAuthenticatorXml accountAuthenticatorXml; + + @Mock + private AccountResponse accountResponse; - private UserService contextUserService; + private String username = "johndoe"; + private char[] qwertyPassword = "qwerty".toCharArray(); + private char[] password = "password".toCharArray(); @Before public void setUp() { - contextUserService = CoreLibrary.getInstance().context().userService(); when(presenter.getOpenSRPContext()).thenReturn(context); + when(sharedPreferences.edit()).thenReturn(sharePrefEditor); + when(allSharedPreferences.getPreferences()).thenReturn(sharedPreferences); when(context.allSharedPreferences()).thenReturn(allSharedPreferences); when(context.userService()).thenReturn(userService); when(presenter.getLoginView()).thenReturn(view); + activity = Robolectric.buildActivity(AppCompatActivity.class).create().get(); + when(view.getActivityContext()).thenReturn(activity); + loginResponseData = new LoginResponseData(); - loginResponseData.user = new User().withUsername(user); + loginResponseData.user = new User().withUsername(username); loginResponseData.time = new Time(new Date(), TimeZone.getTimeZone("Africa/Nairobi")); + + when(context.getHttpAgent()).thenReturn(httpAgent); + when(context.allSettings()).thenReturn(allSettings); + when(accountConfiguration.getAuthorizationEndpoint()).thenReturn("https://my-server.com/oauth/auth"); + when(accountConfiguration.getTokenEndpoint()).thenReturn("https://my-server.com/"); + when(accountConfiguration.getIssuerEndpoint()).thenReturn("https://my-server.com/oauth/issuer"); + when(accountConfiguration.getGrantTypesSupported()).thenReturn(new LinkedList<>(Arrays.asList(new String[]{AccountHelper.OAUTH.GRANT_TYPE.PASSWORD}))); + + when(httpAgent.fetchOAuthConfiguration()).thenReturn(accountConfiguration); + when(accountResponse.getAccessToken()).thenReturn("a-random-access-token"); + + when(httpAgent.oauth2authenticate(ArgumentMatchers.anyString(), ArgumentMatchers.any(char[].class), ArgumentMatchers.eq(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD), ArgumentMatchers.eq("https://my-server.com/"))).thenReturn(accountResponse); + + when(accountAuthenticatorXml.getAccountType()).thenReturn("org.smartregister.core.testapp"); + + Whitebox.setInternalState(CoreLibrary.getInstance(), "context", context); + Whitebox.setInternalState(CoreLibrary.getInstance(), "accountManager", mAccountManager); + Whitebox.setInternalState(CoreLibrary.getInstance(), "authenticatorXml", accountAuthenticatorXml); + + } @After public void tearDown() { - Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", contextUserService); + Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", userService); + Whitebox.setInternalState(CoreLibrary.getInstance(), "context", context); + Whitebox.setInternalState(CoreLibrary.getInstance(), "accountManager", mAccountManager); + Whitebox.setInternalState(CoreLibrary.getInstance(), "authenticatorXml", accountAuthenticatorXml); } @Test @@ -142,141 +203,167 @@ public void testOnDestroyShouldSetPresenterNull() { @Test public void testLoginAttemptsRemoteLoginAndErrorsWithBaseURLIsMissing() { when(allSharedPreferences.fetchBaseURL("")).thenReturn(""); - interactor.login(new WeakReference<>(view), "johndoe", "password"); + interactor.login(new WeakReference<>(view), "johndoe", password); verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(allSharedPreferences).savePreference("DRISHTI_BASE_URL", activity.getString(R.string.opensrp_url)); verify(view).enableLoginButton(true); verify(view).showErrorDialog(activity.getString(R.string.remote_login_base_url_missing_error)); - verify(view, never()).goToHome(anyBoolean()); + verify(view, never()).goToHome(ArgumentMatchers.anyBoolean()); } @Test public void testLoginAttemptsRemoteLoginAndErrorsWithGenericError() { - interactor.login(new WeakReference<>(view), "johndoe", "password"); + interactor.login(new WeakReference<>(view), "johndoe", password); verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(allSharedPreferences, never()).savePreference("DRISHTI_BASE_URL", activity.getString(R.string.opensrp_url)); verify(view, never()).enableLoginButton(true); verify(view).showErrorDialog(activity.getString(R.string.remote_login_generic_error)); - verify(view, never()).goToHome(anyBoolean()); + verify(view, never()).goToHome(ArgumentMatchers.anyBoolean()); } @Test public void testLoginAttemptsRemoteLoginAndErrorsWhenNoInternetConnectivity() { Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", userService); - when(userService.isValidRemoteLogin(user, password)).thenReturn(LoginResponse.NO_INTERNET_CONNECTIVITY); + + when(httpAgent.oauth2authenticate(ArgumentMatchers.anyString(), ArgumentMatchers.any(char[].class), ArgumentMatchers.eq(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD), ArgumentMatchers.eq("https://my-server.com/"))).thenReturn(new AccountResponse(0, new AccountError(0, NO_INTERNET_CONNECTIVITY.name()))); + when(allSharedPreferences.fetchBaseURL("")).thenReturn(activity.getString(R.string.opensrp_url)); - interactor.login(new WeakReference<>(view), user, password); + + interactor.login(new WeakReference<>(view), username, qwertyPassword); + verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(view).enableLoginButton(true); verify(view).showErrorDialog(activity.getString(R.string.no_internet_connectivity)); - verify(view, never()).goToHome(anyBoolean()); + verify(view, never()).goToHome(ArgumentMatchers.anyBoolean()); } @Test public void testLoginAttemptsRemoteLoginAndErrorsWhenNullLoginResponse() { Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", userService); - when(userService.isValidRemoteLogin(user, password)).thenReturn(null); + + when(httpAgent.oauth2authenticate(ArgumentMatchers.anyString(), ArgumentMatchers.any(char[].class), ArgumentMatchers.eq(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD), ArgumentMatchers.eq("https://my-server.com/"))).thenReturn(new AccountResponse(0, new AccountError(0, null))); + when(allSharedPreferences.fetchBaseURL("")).thenReturn(activity.getString(R.string.opensrp_url)); - interactor.login(new WeakReference<>(view), user, password); + + interactor.login(new WeakReference<>(view), username, qwertyPassword); + verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(view).enableLoginButton(true); verify(view).showErrorDialog(activity.getString(R.string.remote_login_generic_error)); - verify(view, never()).goToHome(anyBoolean()); + verify(view, never()).goToHome(ArgumentMatchers.anyBoolean()); } @Test public void testLoginAttemptsRemoteLoginAndErrorsWhenResponseUnknown() { Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", userService); - when(userService.isValidRemoteLogin(user, password)).thenReturn(LoginResponse.UNKNOWN_RESPONSE); + when(httpAgent.oauth2authenticate(ArgumentMatchers.anyString(), ArgumentMatchers.any(char[].class), ArgumentMatchers.eq(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD), ArgumentMatchers.eq("https://my-server.com/"))).thenReturn(new AccountResponse(0, new AccountError(0, UNKNOWN_RESPONSE.name()))); when(allSharedPreferences.fetchBaseURL("")).thenReturn(activity.getString(R.string.opensrp_url)); - interactor.login(new WeakReference<>(view), user, password); + + interactor.login(new WeakReference<>(view), username, qwertyPassword); + verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(view).enableLoginButton(true); verify(view).showErrorDialog(activity.getString(R.string.unknown_response)); - verify(view, never()).goToHome(anyBoolean()); + verify(view, never()).goToHome(ArgumentMatchers.anyBoolean()); } @Test public void testLoginAttemptsRemoteLoginAndErrorsWhenUnauthorized() { Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", userService); - when(userService.isValidRemoteLogin(user, password)).thenReturn(LoginResponse.UNAUTHORIZED); + when(httpAgent.oauth2authenticate(ArgumentMatchers.anyString(), ArgumentMatchers.any(char[].class), ArgumentMatchers.eq(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD), ArgumentMatchers.eq("https://my-server.com/"))).thenReturn(new AccountResponse(0, new AccountError(0, UNAUTHORIZED.name()))); when(allSharedPreferences.fetchBaseURL("")).thenReturn(activity.getString(R.string.opensrp_url)); - interactor.login(new WeakReference<>(view), user, password); + + interactor.login(new WeakReference<>(view), username, qwertyPassword); + verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(view).enableLoginButton(true); verify(view).showErrorDialog(activity.getString(R.string.unauthorized)); - verify(view, never()).goToHome(anyBoolean()); + verify(view, never()).goToHome(ArgumentMatchers.anyBoolean()); } @Test public void testLoginAttemptsRemoteLoginAndErrorsWhenTimeIsWrong() { Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", userService); - when(userService.isValidRemoteLogin(user, password)).thenReturn(LoginResponse.SUCCESS.withPayload(loginResponseData)); - when(userService.validateDeviceTime(any(), anyLong())).thenReturn(TimeStatus.TIME_MISMATCH); - when(userService.isUserInPioneerGroup(user)).thenReturn(true); + + when(httpAgent.oauth2authenticate(ArgumentMatchers.anyString(), ArgumentMatchers.any(char[].class), ArgumentMatchers.eq(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD), ArgumentMatchers.eq("https://my-server.com/"))).thenReturn(accountResponse); + when(userService.fetchUserDetails(ArgumentMatchers.anyString())).thenReturn(LoginResponse.SUCCESS.withPayload(loginResponseData)); + when(userService.saveUserCredentials(username, qwertyPassword, loginResponseData)).thenReturn(userDataBundle); + + when(userService.validateDeviceTime(ArgumentMatchers.any(), ArgumentMatchers.anyLong())).thenReturn(TimeStatus.TIME_MISMATCH); + when(userService.isUserInPioneerGroup(username)).thenReturn(true); when(allSharedPreferences.fetchBaseURL("")).thenReturn(activity.getString(R.string.opensrp_url)); Whitebox.setInternalState(AllConstants.class, "TIME_CHECK", true); - interactor.login(new WeakReference<>(view), user, password); + + interactor.login(new WeakReference<>(view), username, qwertyPassword); + verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(view).enableLoginButton(true); verify(view).showErrorDialog(activity.getString(TimeStatus.TIME_MISMATCH.getMessage())); - verify(view, never()).goToHome(anyBoolean()); + verify(view, never()).goToHome(ArgumentMatchers.anyBoolean()); } @Test public void testLoginAttemptsRemoteLoginAndErrorsWhenTimeZoneIsWrong() { Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", userService); - when(userService.isValidRemoteLogin(user, password)).thenReturn(LoginResponse.SUCCESS.withPayload(loginResponseData)); - when(userService.validateDeviceTime(any(), anyLong())).thenReturn(TimeStatus.TIMEZONE_MISMATCH); - when(userService.isUserInPioneerGroup(user)).thenReturn(true); + when(userService.fetchUserDetails(ArgumentMatchers.anyString())).thenReturn(LoginResponse.SUCCESS.withPayload(loginResponseData)); + when(userService.saveUserCredentials(username, qwertyPassword, loginResponseData)).thenReturn(userDataBundle); + when(userService.validateDeviceTime(ArgumentMatchers.any(), ArgumentMatchers.anyLong())).thenReturn(TimeStatus.TIMEZONE_MISMATCH); + when(userService.isUserInPioneerGroup(username)).thenReturn(true); when(allSharedPreferences.fetchBaseURL("")).thenReturn(activity.getString(R.string.opensrp_url)); Whitebox.setInternalState(AllConstants.class, "TIME_CHECK", true); - interactor.login(new WeakReference<>(view), user, password); + + interactor.login(new WeakReference<>(view), username, qwertyPassword); + verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(view).enableLoginButton(true); verify(view).showErrorDialog(activity.getString(TimeStatus.TIMEZONE_MISMATCH.getMessage(), TimeZone.getTimeZone("Africa/Nairobi").getDisplayName())); - verify(view, never()).goToHome(anyBoolean()); + verify(view, never()).goToHome(ArgumentMatchers.anyBoolean()); } @Test public void testLoginAttemptsRemoteLoginAndErrorsWithErrorFromEnum() { Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", userService); - when(userService.isValidRemoteLogin(user, password)).thenReturn(LoginResponse.SUCCESS_WITH_EMPTY_RESPONSE); - when(userService.validateDeviceTime(any(), anyLong())).thenReturn(TimeStatus.TIMEZONE_MISMATCH); - when(userService.isUserInPioneerGroup(user)).thenReturn(true); + when(httpAgent.oauth2authenticate(ArgumentMatchers.anyString(), ArgumentMatchers.any(char[].class), ArgumentMatchers.eq(AccountHelper.OAUTH.GRANT_TYPE.PASSWORD), ArgumentMatchers.eq("https://my-server.com/"))).thenReturn(new AccountResponse(0, new AccountError(0, SUCCESS_WITH_EMPTY_RESPONSE.name()))); + + when(userService.validateDeviceTime(ArgumentMatchers.any(), ArgumentMatchers.anyLong())).thenReturn(TimeStatus.TIMEZONE_MISMATCH); + when(userService.isUserInPioneerGroup(username)).thenReturn(true); when(allSharedPreferences.fetchBaseURL("")).thenReturn(activity.getString(R.string.opensrp_url)); Whitebox.setInternalState(AllConstants.class, "TIME_CHECK", true); - interactor.login(new WeakReference<>(view), user, password); + + interactor.login(new WeakReference<>(view), username, qwertyPassword); + verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(view).enableLoginButton(true); verify(view).showErrorDialog(LoginResponse.SUCCESS_WITH_EMPTY_RESPONSE.message()); - verify(view, never()).goToHome(anyBoolean()); + verify(view, never()).goToHome(ArgumentMatchers.anyBoolean()); } @Test @Config(shadows = {ShadowNetworkUtils.class}) public void testLoginAttemptsRemoteLoginAndNavigatesToHome() { Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", userService); - when(userService.isValidRemoteLogin(user, password)).thenReturn(LoginResponse.SUCCESS.withPayload(loginResponseData)); - when(userService.validateDeviceTime(any(), anyLong())).thenReturn(TimeStatus.TIMEZONE_MISMATCH); - when(userService.isUserInPioneerGroup(user)).thenReturn(true); + when(userService.fetchUserDetails(ArgumentMatchers.anyString())).thenReturn(LoginResponse.SUCCESS.withPayload(loginResponseData)); + when(userService.validateDeviceTime(ArgumentMatchers.any(), ArgumentMatchers.anyLong())).thenReturn(TimeStatus.TIMEZONE_MISMATCH); + when(userService.isUserInPioneerGroup(username)).thenReturn(true); + when(userService.saveUserCredentials(username, qwertyPassword, loginResponseData)).thenReturn(userDataBundle); when(allSharedPreferences.fetchBaseURL("")).thenReturn(activity.getString(R.string.opensrp_url)); - interactor.login(new WeakReference<>(view), user, password); + + interactor.login(new WeakReference<>(view), username, qwertyPassword); + verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(view).enableLoginButton(true); - verify(userService).remoteLogin(user, password, loginResponseData); verify(view).goToHome(true); } @@ -288,20 +375,23 @@ public void testLoginWithLocalFlagShouldAttemptRemoteLoginAndResetAppForNewUserA Whitebox.setInternalState(interactor, "resetAppHelper", resetAppHelper); when(view.getAppCompatActivity()).thenReturn(activity); when(syncConfiguration.clearDataOnNewTeamLogin()).thenReturn(true); - when(userService.isValidRemoteLogin(user, password)).thenReturn(LoginResponse.SUCCESS.withPayload(loginResponseData)); - when(userService.isUserInPioneerGroup(user)).thenReturn(false); + when(userService.fetchUserDetails(ArgumentMatchers.anyString())).thenReturn(LoginResponse.SUCCESS.withPayload(loginResponseData)); + when(userService.saveUserCredentials(username, qwertyPassword, loginResponseData)).thenReturn(userDataBundle); + when(userService.isUserInPioneerGroup(username)).thenReturn(false); when(allSharedPreferences.fetchBaseURL("")).thenReturn(activity.getString(R.string.opensrp_url)); + interactor = spy(interactor); - interactor.loginWithLocalFlag(new WeakReference<>(view), false, user, password); + interactor.loginWithLocalFlag(new WeakReference<>(view), false, username, qwertyPassword); + verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(view).enableLoginButton(true); verify(view).showClearDataDialog(dialogCaptor.capture()); dialogCaptor.getValue().onClick(dialogInterface, DialogInterface.BUTTON_POSITIVE); verify(dialogInterface).dismiss(); - verify(resetAppHelper).startResetProcess(eq(activity), onCompleteClearDataCaptor.capture()); + verify(resetAppHelper).startResetProcess(ArgumentMatchers.eq(activity), onCompleteClearDataCaptor.capture()); onCompleteClearDataCaptor.getValue().onComplete(); - verify(interactor).login(any(), eq(user), eq(password)); + verify(interactor).login(ArgumentMatchers.any(), ArgumentMatchers.eq(username), ArgumentMatchers.eq(qwertyPassword)); } @@ -313,10 +403,13 @@ public void testLoginWithLocalFlagShouldFailsForDifferentTeam() { Whitebox.setInternalState(CoreLibrary.getInstance(), "syncConfiguration", syncConfiguration); when(syncConfiguration.clearDataOnNewTeamLogin()).thenReturn(false); when(view.getAppCompatActivity()).thenReturn(activity); - when(userService.isValidRemoteLogin(user, password)).thenReturn(LoginResponse.SUCCESS.withPayload(loginResponseData)); - when(userService.isUserInPioneerGroup(user)).thenReturn(false); + when(userService.fetchUserDetails(ArgumentMatchers.anyString())).thenReturn(LoginResponse.SUCCESS.withPayload(loginResponseData)); + when(userService.saveUserCredentials(username, qwertyPassword, loginResponseData)).thenReturn(userDataBundle); + when(userService.isUserInPioneerGroup(username)).thenReturn(false); when(allSharedPreferences.fetchBaseURL("")).thenReturn(activity.getString(R.string.opensrp_url)); - interactor.loginWithLocalFlag(new WeakReference<>(view), false, user, password); + + interactor.loginWithLocalFlag(new WeakReference<>(view), false, username, qwertyPassword); + verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(view).enableLoginButton(true); @@ -331,32 +424,40 @@ public void testLoginWithLocalFlagShouldFailsForDifferentTeam() { @Test public void testLocalLoginShouldShowErrorWhenNotAuthenticated() { Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", userService); - when(allSharedPreferences.fetchForceRemoteLogin()).thenReturn(false); - when(allSharedPreferences.fetchRegisteredANM()).thenReturn(user); - when(userService.isUserInValidGroup(user, password)).thenReturn(false); - interactor.login(new WeakReference<>(view), user, password); + when(allSharedPreferences.fetchForceRemoteLogin(username)).thenReturn(false); + when(allSharedPreferences.fetchRegisteredANM()).thenReturn(username); + when(allSharedPreferences.isRegisteredANM(username)).thenReturn(true); + when(userService.isUserInValidGroup(username, qwertyPassword)).thenReturn(false); + + interactor.login(new WeakReference<>(view), username, qwertyPassword); + verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(view).enableLoginButton(true); verify(view).showErrorDialog(activity.getString(R.string.unauthorized)); - verify(view, never()).goToHome(anyBoolean()); + verify(view, never()).goToHome(ArgumentMatchers.anyBoolean()); } @Test @Config(shadows = {ShadowNetworkUtils.class}) public void testLocalLoginShouldShowNavigateToHomeAndReleaseIds() { Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", userService); - Whitebox.setInternalState(CoreLibrary.getInstance().context(), "uniqueIdRepository", uniqueIdRepository); - when(allSharedPreferences.fetchForceRemoteLogin()).thenReturn(false); - when(allSharedPreferences.fetchRegisteredANM()).thenReturn(user); - when(userService.isUserInValidGroup(user, password)).thenReturn(true); - interactor.login(new WeakReference<>(view), user, password); + when(context.getUniqueIdRepository()).thenReturn(uniqueIdRepository); + when(allSharedPreferences.fetchForceRemoteLogin(username)).thenReturn(false); + when(allSharedPreferences.fetchRegisteredANM()).thenReturn(username); + when(allSharedPreferences.isRegisteredANM(username)).thenReturn(true); + when(userService.isUserInValidGroup(username, qwertyPassword)).thenReturn(true); + when(userService.fetchUserDetails(ArgumentMatchers.anyString())).thenReturn(LoginResponse.SUCCESS.withPayload(loginResponseData)); + when(userService.saveUserCredentials(username, qwertyPassword, loginResponseData)).thenReturn(userDataBundle); + + interactor.login(new WeakReference<>(view), username, qwertyPassword); + verify(view).hideKeyboard(); verify(view).enableLoginButton(false); verify(view).enableLoginButton(true); - verify(view, never()).showErrorDialog(anyString()); + verify(view, never()).showErrorDialog(ArgumentMatchers.anyString()); verify(view).goToHome(false); - verify(userService).localLogin(user, password); + verify(userService).localLoginWith(username); verify(uniqueIdRepository, timeout(ASYNC_TIMEOUT)).releaseReservedIds(); } @@ -364,16 +465,18 @@ public void testLocalLoginShouldShowNavigateToHomeAndReleaseIds() { public void testLocalLoginShouldInitiateRemoteLoginIfTimeCheckEnabled() { Whitebox.setInternalState(CoreLibrary.getInstance().context(), "userService", userService); Whitebox.setInternalState(CoreLibrary.getInstance().context(), "uniqueIdRepository", uniqueIdRepository); - when(allSharedPreferences.fetchForceRemoteLogin()).thenReturn(false); - when(allSharedPreferences.fetchRegisteredANM()).thenReturn(user); - when(userService.isUserInValidGroup(user, password)).thenReturn(true); + when(allSharedPreferences.fetchForceRemoteLogin(username)).thenReturn(false); + when(allSharedPreferences.fetchRegisteredANM()).thenReturn(username); + when(userService.isUserInValidGroup(username, qwertyPassword)).thenReturn(true); Whitebox.setInternalState(AllConstants.class, "TIME_CHECK", true); - when(userService.validateDeviceTime(any(), anyLong())).thenReturn(TimeStatus.TIME_MISMATCH); + when(userService.validateDeviceTime(ArgumentMatchers.any(), ArgumentMatchers.anyLong())).thenReturn(TimeStatus.TIME_MISMATCH); interactor = spy(interactor); - interactor.login(new WeakReference<>(view), user, password); - verify(view, never()).goToHome(anyBoolean()); - verify(userService, never()).localLogin(user, password); - verify(interactor).loginWithLocalFlag(any(), eq(false), eq(user), eq(password)); + + interactor.login(new WeakReference<>(view), username, qwertyPassword); + + verify(view, never()).goToHome(ArgumentMatchers.anyBoolean()); + verify(userService, never()).localLoginWith(username); + verify(interactor).loginWithLocalFlag(ArgumentMatchers.any(), ArgumentMatchers.eq(false), ArgumentMatchers.eq(username), ArgumentMatchers.eq(qwertyPassword)); } @@ -385,18 +488,17 @@ public void testGetFlexValueShouldReturnCorrectFlex() { @Test public void testProcessServerSettingsShouldSaveSettings() throws JSONException { - AllSettings allSettingsContext = CoreLibrary.getInstance().context().allSettings(); - Whitebox.setInternalState(CoreLibrary.getInstance().context(), "allSettings", allSettings); JSONObject jsonObject = new JSONObject(); jsonObject.put(AllConstants.PREF_KEY.SETTINGS, new JSONArray("[{\"identifier\":\"login\",\"serverVersion\":1212121}]")); LoginResponse loginResponse = LoginResponse.SUCCESS.withPayload(loginResponseData); loginResponse.setRawData(jsonObject); + interactor.processServerSettings(loginResponse); + verify(allSettings).put(AllSharedPreferences.LAST_SETTINGS_SYNC_TIMESTAMP, "1212121"); verify(allSettings).putSetting(settingCaptor.capture()); assertEquals("login", settingCaptor.getValue().getKey()); assertEquals("1212121", settingCaptor.getValue().getVersion()); - Whitebox.setInternalState(CoreLibrary.getInstance().context(), "allSettings", allSettingsContext); } diff --git a/opensrp-app/src/test/java/org/smartregister/login/model/BaseLoginModelTest.java b/opensrp-app/src/test/java/org/smartregister/login/model/BaseLoginModelTest.java index 9fe17aa5a..e66804594 100644 --- a/opensrp-app/src/test/java/org/smartregister/login/model/BaseLoginModelTest.java +++ b/opensrp-app/src/test/java/org/smartregister/login/model/BaseLoginModelTest.java @@ -1,8 +1,10 @@ package org.smartregister.login.model; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; +import org.robolectric.util.ReflectionHelpers; import org.smartregister.BaseRobolectricUnitTest; import org.smartregister.Context; import org.smartregister.CoreLibrary; @@ -30,6 +32,11 @@ public void setUp() { loginModel = new BaseLoginModel(); } + @After + public void tearDown() { + ReflectionHelpers.setStaticField(CoreLibrary.class, "instance", null); + } + @Test public void testGetOpenSRPContextShouldReturnContext() { assertNotNull(loginModel.getOpenSRPContext()); @@ -39,8 +46,8 @@ public void testGetOpenSRPContextShouldReturnContext() { @Test public void testIsPasswordValidReturnsCorrectResult() { - assertFalse(loginModel.isPasswordValid("")); - assertTrue(loginModel.isPasswordValid("qwerty120")); + assertFalse(loginModel.isPasswordValid("".toCharArray())); + assertTrue(loginModel.isPasswordValid("qwerty120".toCharArray())); } diff --git a/opensrp-app/src/test/java/org/smartregister/login/presenter/BaseLoginPresenterTest.java b/opensrp-app/src/test/java/org/smartregister/login/presenter/BaseLoginPresenterTest.java index 49372495c..468e5cf0d 100644 --- a/opensrp-app/src/test/java/org/smartregister/login/presenter/BaseLoginPresenterTest.java +++ b/opensrp-app/src/test/java/org/smartregister/login/presenter/BaseLoginPresenterTest.java @@ -6,6 +6,7 @@ import android.widget.RelativeLayout; import android.widget.ScrollView; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Answers; @@ -13,7 +14,9 @@ import org.mockito.Spy; import org.powermock.reflect.Whitebox; import org.robolectric.Robolectric; +import org.robolectric.util.ReflectionHelpers; import org.smartregister.BaseRobolectricUnitTest; +import org.smartregister.CoreLibrary; import org.smartregister.R; import org.smartregister.login.model.BaseLoginModel; import org.smartregister.view.activity.BaseLoginActivityTest.BaseLoginActivityImpl; @@ -60,6 +63,11 @@ public void setUp() { when(loginView.getActivityContext()).thenReturn(activity); } + @After + public void tearDown() { + ReflectionHelpers.setStaticField(CoreLibrary.class, "instance", null); + } + @Test public void testOnDestroyShouldCleanUp() { presenter.onDestroy(false); @@ -77,7 +85,7 @@ public void testGetLoginViewShouldReturnView() { @Test public void testAttemptLoginShouldErrorIfAppIsOutdated() { when(loginView.isAppVersionAllowed()).thenReturn(false); - presenter.attemptLogin("john", "doe"); + presenter.attemptLogin("john", "doe".toCharArray()); verify(loginView).showErrorDialog(activity.getString(R.string.outdated_app)); verify(loginView).isAppVersionAllowed(); verify(loginView).getActivityContext(); @@ -88,7 +96,7 @@ public void testAttemptLoginShouldErrorIfAppIsOutdated() { @Test public void testAttemptLoginShouldNotInvokeLoginAndDisplaysErrors() { when(loginView.isAppVersionAllowed()).thenReturn(true); - presenter.attemptLogin("", ""); + presenter.attemptLogin("", "".toCharArray()); verify(loginView).setPasswordError(R.string.error_invalid_password); verify(loginView).setUsernameError(R.string.error_field_required); verify(loginView).enableLoginButton(true); @@ -98,11 +106,11 @@ public void testAttemptLoginShouldNotInvokeLoginAndDisplaysErrors() { @Test public void testAttemptLoginShouldInvokeLogin() { when(loginView.isAppVersionAllowed()).thenReturn(true); - presenter.attemptLogin("john", "doe"); + presenter.attemptLogin("john", "doe".toCharArray()); verify(loginView, never()).setPasswordError(R.string.error_invalid_password); verify(loginView, never()).setUsernameError(R.string.error_field_required); verify(loginView, never()).enableLoginButton(true); - verify(loginInteractor).login(any(), eq("john"), eq("doe")); + verify(loginInteractor).login(any(), eq("john"), eq("doe".toCharArray())); } @Test diff --git a/opensrp-app/src/test/java/org/smartregister/repository/RepositoryRobolectricTest.java b/opensrp-app/src/test/java/org/smartregister/repository/RepositoryRobolectricTest.java index fa0a0dfd6..f105757ab 100644 --- a/opensrp-app/src/test/java/org/smartregister/repository/RepositoryRobolectricTest.java +++ b/opensrp-app/src/test/java/org/smartregister/repository/RepositoryRobolectricTest.java @@ -12,6 +12,7 @@ import org.smartregister.AllConstants; import org.smartregister.BaseRobolectricUnitTest; import org.smartregister.commonregistry.CommonFtsObject; +import org.smartregister.security.SecurityHelper; import org.smartregister.util.Session; import java.io.File; @@ -135,7 +136,7 @@ public void onCreateShouldCreateFtsTablesWithSortFieldIncluded() { @Test public void canUseThisPasswordShouldCallIsDatabaseWritableAndReturnTrue() { Repository repository = Mockito.mock(Repository.class, Mockito.CALLS_REAL_METHODS); - String password = "mypwd"; + byte[] password = SecurityHelper.toBytes("mypwd".toCharArray()); Mockito.doReturn(true).when(repository).isDatabaseWritable(password); Assert.assertTrue(repository.canUseThisPassword(password)); diff --git a/opensrp-app/src/test/java/org/smartregister/repository/RepositoryTest.java b/opensrp-app/src/test/java/org/smartregister/repository/RepositoryTest.java index b0f8f4c34..75d55d732 100644 --- a/opensrp-app/src/test/java/org/smartregister/repository/RepositoryTest.java +++ b/opensrp-app/src/test/java/org/smartregister/repository/RepositoryTest.java @@ -14,8 +14,7 @@ import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.util.ReflectionHelpers; import org.smartregister.BaseUnitTest; -import org.smartregister.repository.mock.RepositoryMock; -import org.smartregister.util.Session; +import org.smartregister.security.SecurityHelper; import org.smartregister.view.activity.DrishtiApplication; import java.io.File; @@ -29,38 +28,40 @@ public class RepositoryTest extends BaseUnitTest { @Rule public PowerMockRule rule = new PowerMockRule(); - private RepositoryMock repositoryMock; - @Mock private DrishtiApplication drishtiApplication; private Repository repository; + @Mock private android.content.Context context; + @Mock - private Session session; - @Mock - private DrishtiRepository drishtiRepository; + private SQLiteDatabase database; private String dbName; - private String password; + private char[] password; @Before public void setUp() { dbName = "drishti.db"; - password = "Android7832!"; + password = "Android7832!".toCharArray(); MockitoAnnotations.initMocks(this); PowerMockito.mockStatic(DrishtiApplication.class); PowerMockito.when(DrishtiApplication.getInstance()).thenReturn(drishtiApplication); PowerMockito.when(drishtiApplication.getApplicationContext()).thenReturn(context); - Mockito.doReturn(password).when(drishtiApplication).getPassword(); + + ReflectionHelpers.setField(drishtiApplication, "password", SecurityHelper.toBytes(password)); PowerMockito.when(context.getDir("opensrp", android.content.Context.MODE_PRIVATE)).thenReturn(new File("/")); - repository = Mockito.mock(Repository.class, Mockito.CALLS_REAL_METHODS); + repository = Mockito.spy(Mockito.mock(Repository.class, Mockito.CALLS_REAL_METHODS)); ReflectionHelpers.setField(repository, "context", context); ReflectionHelpers.setField(repository, "dbName", dbName); + + Mockito.doReturn(true).when(database).isOpen(); + ReflectionHelpers.setField(repository, "mDatabase", database); } @Test @@ -69,7 +70,7 @@ public void getReadableDatabaseShouldCallGetReadableDbAndPassword() { repository.getReadableDatabase(); - Mockito.verify(repository).getReadableDatabase(password); + Mockito.verify(repository).getReadableDatabase(SecurityHelper.toBytes(password)); } @Test(expected = RuntimeException.class) @@ -83,9 +84,10 @@ public void getReadableDatabaseShouldThrowRuntimeException() { public void getWritableDatabaseShouldCallGetWritableDbAndPassword() { Mockito.doReturn(null).when(repository).getWritableDatabase(password); + repository.getWritableDatabase(); - Mockito.verify(repository).getWritableDatabase(password); + Mockito.verify(repository).getWritableDatabase(SecurityHelper.toBytes(password)); } @Test(expected = RuntimeException.class) diff --git a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java index 2ea30358a..7cc1bd151 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java @@ -199,6 +199,7 @@ public void shouldDeleteDataAndSettingsWhenLogoutHappens() throws Exception { SyncConfiguration syncConfiguration = mock(SyncConfiguration.class); Mockito.doReturn(false).when(syncConfiguration).clearDataOnNewTeamLogin(); ReflectionHelpers.setField(CoreLibrary.getInstance(), "syncConfiguration", syncConfiguration); + Whitebox.setInternalState(drishtiApplication, "password", password); userService.logout(); @@ -357,7 +358,7 @@ public void testIsUserInValidGroupShouldReturnFalseOnError() throws Exception { when(keyStore.getEntry(user, null)).thenReturn(privateKeyEntry); String password = UUID.randomUUID().toString(); assertFalse(userService.isUserInValidGroup(user, password.toCharArray())); - // verify(allSharedPreferences, never()).fetchEncryptedGroupId(user); + verify(allSharedPreferences, never()).fetchEncryptedGroupId(user); verifyZeroInteractions(repository); } diff --git a/opensrp-app/src/test/java/org/smartregister/sync/intent/SyncIntentServiceTest.java b/opensrp-app/src/test/java/org/smartregister/sync/intent/SyncIntentServiceTest.java index d0fb37b25..ed345a4a3 100644 --- a/opensrp-app/src/test/java/org/smartregister/sync/intent/SyncIntentServiceTest.java +++ b/opensrp-app/src/test/java/org/smartregister/sync/intent/SyncIntentServiceTest.java @@ -104,7 +104,7 @@ public void testHandleSyncCallsLogoutUserIfHasValidAuthorizationIsFalse() throws } @Test - public void testHandleSyncCallsLogOutUserIfAppVersionIsNotAllowedAnd() { + public void testHandleSyncCallsLogOutUserIfAppVersionIsNotAllowedAnd() throws AuthenticatorException, OperationCanceledException, IOException { syncIntentService = spy(syncIntentService); Whitebox.setInternalState(syncIntentService, "syncUtils", syncUtils); when(syncUtils.verifyAuthorization()).thenReturn(true); diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/BaseLoginActivityTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/BaseLoginActivityTest.java index d5b75ec95..f4bb8d601 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/BaseLoginActivityTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/BaseLoginActivityTest.java @@ -17,6 +17,7 @@ import android.widget.Button; import android.widget.EditText; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; @@ -65,6 +66,11 @@ public void setUp() { baseLoginActivity = Mockito.spy(controller.get()); } + @After + public void tearDown() { + resetCoreLibrary(); + } + @Test public void onCreateShouldCallSetupOperations() { // Setup again for diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/BaseRegisterActivityTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/BaseRegisterActivityTest.java index 9cc97b63c..b088d9b6f 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/BaseRegisterActivityTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/BaseRegisterActivityTest.java @@ -13,6 +13,7 @@ import com.google.android.gms.vision.barcode.Barcode; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -22,6 +23,7 @@ import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowActivity; import org.robolectric.shadows.ShadowToast; +import org.robolectric.util.ReflectionHelpers; import org.smartregister.AllConstants; import org.smartregister.AllConstants.BARCODE; import org.smartregister.BaseRobolectricUnitTest; @@ -85,10 +87,15 @@ public class BaseRegisterActivityTest extends BaseRobolectricUnitTest { @Before public void setUp() { Whitebox.setInternalState(CoreLibrary.getInstance().context(), "ziggyService", ziggyService); - controller = Robolectric.buildActivity(BaseRegisterActivityMock.class).create().start().resume(); + controller = Robolectric.buildActivity(BaseRegisterActivityMock.class).create().start(); activity = controller.get(); } + @After + public void tearDown() { + ReflectionHelpers.setStaticField(CoreLibrary.class, "instance", null); + } + @Test public void testOnCreate() { diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/mock/BaseRegisterActivityMock.java b/opensrp-app/src/test/java/org/smartregister/view/activity/mock/BaseRegisterActivityMock.java index d61a59876..5daab2344 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/mock/BaseRegisterActivityMock.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/mock/BaseRegisterActivityMock.java @@ -6,6 +6,7 @@ import android.view.View; import org.json.JSONObject; +import org.smartregister.Context; import org.smartregister.R; import org.smartregister.view.activity.BaseRegisterActivity; import org.smartregister.view.contract.BaseRegisterContract; @@ -29,6 +30,11 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } + @Override + protected Context context(){ + return mock(Context.class); + } + @Override protected void initializePresenter() { presenter = mock(BaseRegisterContract.Presenter.class); diff --git a/opensrp-app/src/test/java/org/smartregister/view/receiver/ConnectivityChangeReceiverTest.java b/opensrp-app/src/test/java/org/smartregister/view/receiver/ConnectivityChangeReceiverTest.java index 164564fd0..bb21291a5 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/receiver/ConnectivityChangeReceiverTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/receiver/ConnectivityChangeReceiverTest.java @@ -39,11 +39,13 @@ public void setUp() throws Exception { } @After - public void tearDown() throws Exception { + public void tearDown(){ // Log out the user Session session = ReflectionHelpers.getField(CoreLibrary.getInstance().context().userService(), "session"); session.setPassword(null); session.start(0); + ReflectionHelpers.setStaticField(CoreLibrary.class, "instance", null); + } @Test From 064a5f52c9bf9614a330c163ebecaeda75412128 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Wed, 26 Aug 2020 18:52:29 +0300 Subject: [PATCH 67/70] Fix bug :bug: - Error After Cancelling Log in by user from a Different Team --- gradle.properties | 2 +- .../org/smartregister/service/UserService.java | 14 +++++++------- .../org/smartregister/util/CredentialsHelper.java | 4 +--- .../view/activity/DrishtiApplication.java | 4 ++-- .../org/smartregister/service/UserServiceTest.java | 4 ++-- .../smartregister/util/CredentialsHelperTest.java | 4 ++-- .../view/activity/DrishtiApplicationTest.java | 3 ++- 7 files changed, 17 insertions(+), 18 deletions(-) diff --git a/gradle.properties b/gradle.properties index b98ca1aa7..ecaefd389 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=2.0.5-SNAPSHOT +VERSION_NAME=2.0.6-SNAPSHOT VERSION_CODE=1 GROUP=org.smartregister POM_SETTING_DESCRIPTION=OpenSRP Client Core Application diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index b016d8864..6b34a8131 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -223,13 +223,13 @@ public boolean isUserInValidGroup(final String userName, final char[] password) try { // Compare stored password hash with provided password hash - storedHash = getLocalAuthenticationCredentials(); + storedHash = getLocalAuthenticationCredentials(userName); passwordHash = generatePasswordHash(userName, password); if (storedHash != null && Arrays.equals(storedHash, passwordHash)) { - return isValidDBPassword(getDBAuthenticationCredentials()); + return isValidDBPassword(getDBAuthenticationCredentials(userName)); } } catch (Exception e) { Timber.e(e); @@ -260,13 +260,13 @@ protected byte[] generatePasswordHash(String userName, char[] password) { @VisibleForTesting - protected byte[] getLocalAuthenticationCredentials() { - return DrishtiApplication.getInstance().credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.LOCAL_AUTH); + protected byte[] getLocalAuthenticationCredentials(String username) { + return DrishtiApplication.getInstance().credentialsProvider().getCredentials(username, CredentialsHelper.CREDENTIALS_TYPE.LOCAL_AUTH); } @VisibleForTesting - protected byte[] getDBAuthenticationCredentials() { - return DrishtiApplication.getInstance().credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); + protected byte[] getDBAuthenticationCredentials(String username) { + return DrishtiApplication.getInstance().credentialsProvider().getCredentials(username, CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); } @@ -594,7 +594,7 @@ public Bundle saveUserCredentials(String userName, char[] password, LoginRespons if (CredentialsHelper.shouldMigrate()) { byte[] passphrase = DrishtiApplication.getInstance().credentialsProvider().generateDBCredentials(SecurityHelper.toChars(localAuthHash.getPassword()), userInfo); - byte[] oldPassword = allSharedPreferences.getDBEncryptionVersion() == 0 ? getGroupId(username) : DrishtiApplication.getInstance().credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); + byte[] oldPassword = allSharedPreferences.getDBEncryptionVersion() == 0 ? getGroupId(username) : DrishtiApplication.getInstance().credentialsProvider().getCredentials(username, CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); if (oldPassword != null && !Arrays.equals(passphrase, oldPassword)) { try { diff --git a/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java b/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java index f680a999e..f48f76f9d 100644 --- a/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/util/CredentialsHelper.java @@ -32,9 +32,7 @@ public static boolean shouldMigrate() { BuildConfig.DB_ENCRYPTION_VERSION > CoreLibrary.getInstance().context().allSharedPreferences().getDBEncryptionVersion()); } - public byte[] getCredentials(String type) { - - String username = allSharedPreferences.fetchRegisteredANM(); + public byte[] getCredentials(String username, String type) { if (CREDENTIALS_TYPE.DB_AUTH.equals(type)) { diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java index 46c7adf60..31660cb18 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/DrishtiApplication.java @@ -48,7 +48,7 @@ public static synchronized X getInstance() { } @Nullable - public P2PClassifier getP2PClassifier(){ + public P2PClassifier getP2PClassifier() { return null; } @@ -131,7 +131,7 @@ public CredentialsHelper credentialsProvider() { public final byte[] getPassword() { if (password == null) { - password = credentialsProvider().getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); + password = credentialsProvider().getCredentials(getUsername(), CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); } return password; diff --git a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java index 7cc1bd151..4793d572b 100644 --- a/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java +++ b/opensrp-app/src/test/java/org/smartregister/service/UserServiceTest.java @@ -339,8 +339,8 @@ public void testIsUserInValidGroupForValidUserAndPassword() throws Exception { when(repository.canUseThisPassword(password)).thenReturn(true); doReturn(password).when(userService).generatePasswordHash(userName, SecurityHelper.toChars(password)); - doReturn(password).when(userService).getLocalAuthenticationCredentials(); - doReturn(password).when(userService).getDBAuthenticationCredentials(); + doReturn(password).when(userService).getLocalAuthenticationCredentials(userName); + doReturn(password).when(userService).getDBAuthenticationCredentials(userName); assertTrue(userService.isUserInValidGroup(userName, SecurityHelper.toChars(password))); verify(repository).canUseThisPassword(password); diff --git a/opensrp-app/src/test/java/org/smartregister/util/CredentialsHelperTest.java b/opensrp-app/src/test/java/org/smartregister/util/CredentialsHelperTest.java index b8b2376bf..ab44d7da8 100644 --- a/opensrp-app/src/test/java/org/smartregister/util/CredentialsHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/util/CredentialsHelperTest.java @@ -94,7 +94,7 @@ public void testShouldMigrateReturnsTrueForDBEncryptionVersionZero() { @Test public void testGetCredentialsInvokesGetDecryptedPassphraseValueWithCorrectValuesForDBAuth() { - credentialsHelper.getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); + credentialsHelper.getCredentials(TEST_USERNAME, CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); ArgumentCaptor usernameArgCaptor = ArgumentCaptor.forClass(String.class); Mockito.verify(userService, Mockito.times(1)).getDecryptedPassphraseValue(usernameArgCaptor.capture()); @@ -104,7 +104,7 @@ public void testGetCredentialsInvokesGetDecryptedPassphraseValueWithCorrectValue @Test public void testGetCredentialsInvokesGetDecryptedPassphraseValueWithCorrectValuesForLocalAuth() { - credentialsHelper.getCredentials(CredentialsHelper.CREDENTIALS_TYPE.LOCAL_AUTH); + credentialsHelper.getCredentials(TEST_USERNAME, CredentialsHelper.CREDENTIALS_TYPE.LOCAL_AUTH); ArgumentCaptor usernameArgCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor keyArgCaptor = ArgumentCaptor.forClass(String.class); diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java index 9357c9ea2..262fd16ec 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java @@ -8,6 +8,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @@ -75,7 +76,7 @@ public void getPassword() { Assert.assertNull(ReflectionHelpers.getField(drishtiApplication, "password")); CredentialsHelper credentialsProvider = Mockito.spy(new CredentialsHelper(Mockito.mock(Context.class))); - Mockito.doReturn(password).when(credentialsProvider).getCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); + Mockito.doReturn(password).when(credentialsProvider).getCredentials(ArgumentMatchers.anyString(), CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); ReflectionHelpers.setField(drishtiApplication, "credentialsHelper", credentialsProvider); From 1933811f87f3c793eefc75f6e2d89628c1b2f1c7 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Thu, 27 Aug 2020 12:40:20 +0300 Subject: [PATCH 68/70] Fix bug :bug: - Login fails for different team provider login on the first time - Incorrect provider info shown on different team p authentication dialog - Fix failing tests :white_check_mark: --- .../smartregister/login/interactor/BaseLoginInteractor.java | 3 ++- .../org/smartregister/repository/AllSharedPreferences.java | 1 - .../main/java/org/smartregister/security/SecurityHelper.java | 2 -- .../src/main/java/org/smartregister/service/UserService.java | 1 - .../org/smartregister/view/activity/BaseLoginActivity.java | 5 +++++ .../org/smartregister/view/contract/BaseLoginContract.java | 3 +++ .../smartregister/view/activity/DrishtiApplicationTest.java | 2 +- 7 files changed, 11 insertions(+), 6 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java index c44de01d7..8e15fe518 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java +++ b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java @@ -23,6 +23,7 @@ import org.smartregister.login.task.RemoteLoginTask; import org.smartregister.multitenant.ResetAppHelper; import org.smartregister.repository.AllSharedPreferences; +import org.smartregister.security.SecurityHelper; import org.smartregister.service.UserService; import org.smartregister.sync.helper.ServerSettingsHelper; import org.smartregister.util.NetworkUtils; @@ -162,7 +163,7 @@ private void remoteLogin(final String userName, final char[] password, final Acc dialog.dismiss(); if (which == DialogInterface.BUTTON_POSITIVE) { - resetAppHelper.startResetProcess(getLoginView().getAppCompatActivity(), () -> login(new WeakReference<>(getLoginView()), userName, password)); + resetAppHelper.startResetProcess(getLoginView().getAppCompatActivity(), () -> login(new WeakReference<>(getLoginView()), userName, SecurityHelper.readValue(getLoginView().getPasswordEditText().getText()))); } }); } else { diff --git a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java index 0e1bc7c5b..6d126c5cb 100644 --- a/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java +++ b/opensrp-app/src/main/java/org/smartregister/repository/AllSharedPreferences.java @@ -373,6 +373,5 @@ public int getDBEncryptionVersion() { public void setDBEncryptionVersion(int encryptionVersion) { preferences.edit().putInt(DB_ENCRYPTION_VERSION, encryptionVersion).commit(); } - } diff --git a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java index d4adbe870..47bc99d7a 100644 --- a/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/security/SecurityHelper.java @@ -41,8 +41,6 @@ public static char[] readValue(Editable editable) { char[] chars = new char[editable.length()]; editable.getChars(0, editable.length(), chars, 0); - editable.clear(); - return chars; } diff --git a/opensrp-app/src/main/java/org/smartregister/service/UserService.java b/opensrp-app/src/main/java/org/smartregister/service/UserService.java index 6b34a8131..ecc0201dc 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/UserService.java +++ b/opensrp-app/src/main/java/org/smartregister/service/UserService.java @@ -618,7 +618,6 @@ public Bundle saveUserCredentials(String userName, char[] password, LoginRespons allSharedPreferences.savePioneerUser(username); } - allSharedPreferences.updateANMUserName(userName);//update current logged in user } } catch (Exception e) { diff --git a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java index c370415db..8e2221dee 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java +++ b/opensrp-app/src/main/java/org/smartregister/view/activity/BaseLoginActivity.java @@ -129,6 +129,10 @@ private void initializeLoginChildViews() { loginButton.setOnClickListener(this); } + public EditText getPasswordEditText() { + return passwordEditText; + } + private void initializeProgressDialog() { progressDialog = new ProgressDialog(this); progressDialog.setCancelable(false); @@ -253,6 +257,7 @@ public void resetPaswordError() { passwordEditText.setError(null); } + @Override public Activity getActivityContext() { return this; diff --git a/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java b/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java index 8569a8aa3..df856d76e 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java +++ b/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java @@ -4,6 +4,7 @@ import android.content.DialogInterface; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; +import android.widget.EditText; import java.lang.ref.WeakReference; @@ -63,6 +64,8 @@ interface View { String getAuthTokenType(); boolean isNewAccount(); + + EditText getPasswordEditText(); } interface Interactor { diff --git a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java index 262fd16ec..d616927c5 100644 --- a/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java +++ b/opensrp-app/src/test/java/org/smartregister/view/activity/DrishtiApplicationTest.java @@ -76,7 +76,7 @@ public void getPassword() { Assert.assertNull(ReflectionHelpers.getField(drishtiApplication, "password")); CredentialsHelper credentialsProvider = Mockito.spy(new CredentialsHelper(Mockito.mock(Context.class))); - Mockito.doReturn(password).when(credentialsProvider).getCredentials(ArgumentMatchers.anyString(), CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH); + Mockito.doReturn(password).when(credentialsProvider).getCredentials(ArgumentMatchers.anyString(), ArgumentMatchers.eq(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH)); ReflectionHelpers.setField(drishtiApplication, "credentialsHelper", credentialsProvider); From 35be648327500eab45bf68d7f3a39e56235aac82 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Thu, 27 Aug 2020 17:54:43 +0300 Subject: [PATCH 69/70] Fix tests + build :white_check_mark: :green_heart: --- .../login/interactor/BaseLoginInteractor.java | 5 ++--- .../login/presenter/BaseLoginPresenter.java | 6 ++++++ .../smartregister/multitenant/ResetAppHelper.java | 15 ++++++--------- .../view/contract/BaseLoginContract.java | 2 ++ .../login/interactor/BaseLoginInteractorTest.java | 1 + .../security/SecurityHelperTest.java | 1 - 6 files changed, 17 insertions(+), 13 deletions(-) diff --git a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java index 8e15fe518..42c3d0221 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java +++ b/opensrp-app/src/main/java/org/smartregister/login/interactor/BaseLoginInteractor.java @@ -23,7 +23,6 @@ import org.smartregister.login.task.RemoteLoginTask; import org.smartregister.multitenant.ResetAppHelper; import org.smartregister.repository.AllSharedPreferences; -import org.smartregister.security.SecurityHelper; import org.smartregister.service.UserService; import org.smartregister.sync.helper.ServerSettingsHelper; import org.smartregister.util.NetworkUtils; @@ -163,7 +162,7 @@ private void remoteLogin(final String userName, final char[] password, final Acc dialog.dismiss(); if (which == DialogInterface.BUTTON_POSITIVE) { - resetAppHelper.startResetProcess(getLoginView().getAppCompatActivity(), () -> login(new WeakReference<>(getLoginView()), userName, SecurityHelper.readValue(getLoginView().getPasswordEditText().getText()))); + resetAppHelper.startResetProcess(getLoginView().getAppCompatActivity(), () -> login(new WeakReference<>(getLoginView()), userName, mLoginPresenter.getPassword())); } }); } else { @@ -286,4 +285,4 @@ protected void processServerSettings(LoginResponse loginResponse) { } } } -} \ No newline at end of file +} diff --git a/opensrp-app/src/main/java/org/smartregister/login/presenter/BaseLoginPresenter.java b/opensrp-app/src/main/java/org/smartregister/login/presenter/BaseLoginPresenter.java index d5d2368cd..053edd60d 100644 --- a/opensrp-app/src/main/java/org/smartregister/login/presenter/BaseLoginPresenter.java +++ b/opensrp-app/src/main/java/org/smartregister/login/presenter/BaseLoginPresenter.java @@ -11,6 +11,7 @@ import org.smartregister.Context; import org.smartregister.R; import org.smartregister.repository.AllSharedPreferences; +import org.smartregister.security.SecurityHelper; import org.smartregister.view.contract.BaseLoginContract; import java.lang.ref.WeakReference; @@ -137,6 +138,11 @@ public void setLanguage() { resources.updateConfiguration(configuration, displayMetrics); } + @Override + public char[] getPassword() { + return SecurityHelper.readValue(getLoginView().getPasswordEditText().getText()); + } + @Override public Context getOpenSRPContext() { return mLoginModel.getOpenSRPContext(); diff --git a/opensrp-app/src/main/java/org/smartregister/multitenant/ResetAppHelper.java b/opensrp-app/src/main/java/org/smartregister/multitenant/ResetAppHelper.java index 90fd16309..fc6f85cc7 100644 --- a/opensrp-app/src/main/java/org/smartregister/multitenant/ResetAppHelper.java +++ b/opensrp-app/src/main/java/org/smartregister/multitenant/ResetAppHelper.java @@ -127,14 +127,11 @@ public void run() { Timber.w("User %s has completely reset the app", application.getUsername()); performResetOperations(); appExecutors.mainThread() - .execute(new Runnable() { - @Override - public void run() { - dismissDialog(); + .execute(() -> { + dismissDialog(); - if (onCompleteClearDataCallback != null) { - onCompleteClearDataCallback.onComplete(); - } + if (onCompleteClearDataCallback != null) { + onCompleteClearDataCallback.onComplete(); } }); @@ -207,9 +204,9 @@ public boolean removePreResetAppCheck(@NonNull PreResetAppCheck preResetAppCheck @Nullable public PreResetAppCheck removePreResetAppCheck(@NonNull String checkName) { - for (PreResetAppCheck preResetAppCheck: preResetAppChecks) { + for (PreResetAppCheck preResetAppCheck : preResetAppChecks) { if (checkName.equals(preResetAppCheck.getUniqueName())) { - if(removePreResetAppCheck(preResetAppCheck)) { + if (removePreResetAppCheck(preResetAppCheck)) { return preResetAppCheck; } diff --git a/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java b/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java index df856d76e..828c9ca91 100644 --- a/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java +++ b/opensrp-app/src/main/java/org/smartregister/view/contract/BaseLoginContract.java @@ -28,6 +28,8 @@ interface Presenter { org.smartregister.Context getOpenSRPContext(); boolean isServerSettingsSet(); + + char[] getPassword(); } interface View { diff --git a/opensrp-app/src/test/java/org/smartregister/login/interactor/BaseLoginInteractorTest.java b/opensrp-app/src/test/java/org/smartregister/login/interactor/BaseLoginInteractorTest.java index cfaaff446..395783d79 100644 --- a/opensrp-app/src/test/java/org/smartregister/login/interactor/BaseLoginInteractorTest.java +++ b/opensrp-app/src/test/java/org/smartregister/login/interactor/BaseLoginInteractorTest.java @@ -155,6 +155,7 @@ public void setUp() { when(context.allSharedPreferences()).thenReturn(allSharedPreferences); when(context.userService()).thenReturn(userService); when(presenter.getLoginView()).thenReturn(view); + when(presenter.getPassword()).thenReturn(qwertyPassword); activity = Robolectric.buildActivity(AppCompatActivity.class).create().get(); diff --git a/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java b/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java index d367a921e..1e419e8fc 100644 --- a/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java +++ b/opensrp-app/src/test/java/org/smartregister/security/SecurityHelperTest.java @@ -64,7 +64,6 @@ public void testReadValueClearsEditableAfterReadingValue() { ArgumentCaptor lastArgCaptor = ArgumentCaptor.forClass(Integer.class); Mockito.verify(editable).getChars(firstArgCaptor.capture(), lengthCaptor.capture(), charsCaptor.capture(), lastArgCaptor.capture()); - Mockito.verify(editable).clear(); Assert.assertEquals(2, lengthCaptor.getValue().intValue()); Assert.assertEquals(0, firstArgCaptor.getValue().intValue()); From f5df6c4cdc0088428f37fb18ac68db3554d479f7 Mon Sep 17 00:00:00 2001 From: Martin Ndegwa Date: Tue, 1 Sep 2020 10:52:00 +0300 Subject: [PATCH 70/70] Update java docs :pencil: - Update java docs for HTTPAgent.initializeHttp --- .../src/main/java/org/smartregister/service/HTTPAgent.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java index c992396ca..7a5107a55 100644 --- a/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java +++ b/opensrp-app/src/main/java/org/smartregister/service/HTTPAgent.java @@ -114,6 +114,10 @@ public HTTPAgent(Context context, AllSharedPreferences /** * This method initializes httpurlconnection + * + * @param requestURLPath This is the url to open http connection to. + * @param setOauthToken A boolean to flag whether to set the OAuth2 bearer access token in the Authorization header of request. + * @return HttpURLConnection Http connection to the OpenSRP server. */ private HttpURLConnection initializeHttp(String requestURLPath, boolean setOauthToken) throws IOException {