diff --git a/README.md b/README.md index b2249709..3e90ffa0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![License](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) [![Circle CI](https://circleci.com/gh/vase4kin/TeamCityApp/tree/master.svg?style=shield)](https://circleci.com/gh/vase4kin/TeamCityApp/tree/master) [![codecov](https://codecov.io/gh/vase4kin/TeamCityApp/branch/master/graph/badge.svg)](https://codecov.io/gh/vase4kin/TeamCityApp) -[![Release](https://img.shields.io/badge/release-1.2.8-blue.svg)](https://github.com/vase4kin/TeamCityApp/releases/latest) +[![Release](https://img.shields.io/badge/release-1.2.9-blue.svg)](https://github.com/vase4kin/TeamCityApp/releases/latest) ``` diff --git a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/account/create/view/CreateAccountActivityTest.java b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/account/create/view/CreateAccountActivityTest.java index b3375ba8..f829dcb3 100644 --- a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/account/create/view/CreateAccountActivityTest.java +++ b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/account/create/view/CreateAccountActivityTest.java @@ -73,6 +73,7 @@ import static android.support.test.espresso.matcher.ViewMatchers.withText; import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_AUTH; import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_BASE; +import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_BASE_UNSAFE; import static com.github.vase4kin.teamcityapp.dagger.modules.Mocks.URL; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; @@ -109,6 +110,10 @@ public void setComponent(AppComponent appComponent) { @Mock private OkHttpClient mClientBase; + @Named(CLIENT_BASE_UNSAFE) + @Mock + private OkHttpClient unsafeOkHttpClient; + @Named(CLIENT_AUTH) @Mock private OkHttpClient mClientAuth; @@ -126,6 +131,7 @@ public void setUp() { TeamCityApplication app = (TeamCityApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); app.getAppInjector().sharedUserStorage().clearAll(); when(mClientBase.newCall(Matchers.any(Request.class))).thenReturn(mCall); + when(unsafeOkHttpClient.newCall(Matchers.any(Request.class))).thenReturn(mCall); mActivityRule.launchActivity(null); } @@ -165,6 +171,49 @@ public Object answer(InvocationOnMock invocation) throws Throwable { SharedUserStorage storageUtils = SharedUserStorage.init(mActivityRule.getActivity(), null); assertThat(storageUtils.hasGuestAccountWithUrl(URL), is(true)); assertThat(storageUtils.getActiveUser().getTeamcityUrl(), is(URL)); + assertThat(storageUtils.getActiveUser().isSslDisabled(), is(false)); + } + + /** + * Verifies that user can be logged in as guest user with correct account url ignoring ssl + */ + @Test + public void testUserCanCreateGuestUserAccountWithCorrectUrlIgnoringSsl() throws Throwable { + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + mCallbackArgumentCaptor.getValue().onResponse( + mCall, + new Response.Builder() + .request(new Request.Builder().url(URL).build()) + .protocol(Protocol.HTTP_1_0) + .code(200) + .message("") + .build()); + return null; + } + }).when(mCall).enqueue(mCallbackArgumentCaptor.capture()); + + onView(withId(R.id.teamcity_url)).perform(typeText(INPUT_URL), closeSoftKeyboard()); + onView(withId(R.id.guest_user_switch)).perform(click()); + onView(withId(R.id.disable_ssl_switch)).perform(click()); + onView(withText(R.string.warning_ssl_dialog_content)).check(matches(isDisplayed())); + onView(withText(R.string.dialog_ok_title)).perform(click()); + onView(withId(R.id.action_create)).perform(click()); + + intended(allOf( + hasComponent(RootProjectsActivity.class.getName()), + hasFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_SINGLE_TOP), + hasExtras(allOf( + hasEntry(equalTo(BundleExtractorValues.IS_NEW_ACCOUNT_CREATED), equalTo(true)), + hasEntry(equalTo(BundleExtractorValues.IS_REQUIRED_TO_RELOAD), equalTo(true)))), + toPackage("com.github.vase4kin.teamcityapp.mock.debug"))); + + SharedUserStorage storageUtils = SharedUserStorage.init(mActivityRule.getActivity(), null); + assertThat(storageUtils.hasGuestAccountWithUrl(URL), is(true)); + assertThat(storageUtils.getActiveUser().getTeamcityUrl(), is(URL)); + assertThat(storageUtils.getActiveUser().isSslDisabled(), is(true)); } /** diff --git a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/artifact/view/ArtifactListFragmentTest.java b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/artifact/view/ArtifactListFragmentTest.java index 564e7500..73622068 100644 --- a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/artifact/view/ArtifactListFragmentTest.java +++ b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/artifact/view/ArtifactListFragmentTest.java @@ -127,7 +127,7 @@ public static void disableOnboarding() { public void setUp() { TeamCityApplication app = (TeamCityApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); app.getRestApiInjector().sharedUserStorage().clearAll(); - app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL); + app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL, false); ConditionWatcher.setTimeoutLimit(TIMEOUT); } diff --git a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/buildlist/view/BuildListActivityTest.java b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/buildlist/view/BuildListActivityTest.java index 3f6ea472..b0c5ae7b 100644 --- a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/buildlist/view/BuildListActivityTest.java +++ b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/buildlist/view/BuildListActivityTest.java @@ -112,7 +112,7 @@ public static void disableOnboarding() { public void setUp() { TeamCityApplication app = (TeamCityApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); app.getRestApiInjector().sharedUserStorage().clearAll(); - app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL); + app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL, false); } @Test diff --git a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/drawer/DrawerTest.java b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/drawer/DrawerTest.java index 33754e45..6b4cbf02 100644 --- a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/drawer/DrawerTest.java +++ b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/drawer/DrawerTest.java @@ -85,7 +85,7 @@ public static void disableOnboarding() { public void setUp() { TeamCityApplication app = (TeamCityApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); app.getRestApiInjector().sharedUserStorage().clearAll(); - app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL); + app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL, false); mActivityRule.launchActivity(null); } diff --git a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/filter_builds/view/FilterBuildsActivityTest.java b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/filter_builds/view/FilterBuildsActivityTest.java index f7d755c5..e5350734 100644 --- a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/filter_builds/view/FilterBuildsActivityTest.java +++ b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/filter_builds/view/FilterBuildsActivityTest.java @@ -100,7 +100,7 @@ public static void disableOnboarding() { public void setUp() { TeamCityApplication app = (TeamCityApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); app.getRestApiInjector().sharedUserStorage().clearAll(); - app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL); + app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL, false); when(mTeamCityService.listBuilds(anyString(), anyString())).thenReturn(Observable.just(new Builds(0, Collections.emptyList()))); } diff --git a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/filter_builds/view/FilterBuildsBranchesAutoCompleteTest.java b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/filter_builds/view/FilterBuildsBranchesAutoCompleteTest.java index 7c213534..8bb0911a 100644 --- a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/filter_builds/view/FilterBuildsBranchesAutoCompleteTest.java +++ b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/filter_builds/view/FilterBuildsBranchesAutoCompleteTest.java @@ -92,7 +92,7 @@ public void setComponent(RestApiComponent restApiComponent) { public void setUp() { TeamCityApplication app = (TeamCityApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); app.getRestApiInjector().sharedUserStorage().clearAll(); - app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL); + app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL, false); } @Test diff --git a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/login/view/LoginActivityTest.java b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/login/view/LoginActivityTest.java index 60415081..d0355856 100644 --- a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/login/view/LoginActivityTest.java +++ b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/login/view/LoginActivityTest.java @@ -69,6 +69,7 @@ import static android.support.test.espresso.matcher.ViewMatchers.withText; import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_AUTH; import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_BASE; +import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_BASE_UNSAFE; import static com.github.vase4kin.teamcityapp.dagger.modules.Mocks.URL; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; @@ -104,7 +105,11 @@ public void setComponent(AppComponent appComponent) { @Named(CLIENT_BASE) @Mock - private OkHttpClient mClientBase; + private OkHttpClient okHttpClient; + + @Named(CLIENT_BASE_UNSAFE) + @Mock + private OkHttpClient unsafeOkHttpClient; @Named(CLIENT_AUTH) @Mock @@ -120,7 +125,8 @@ public void setComponent(AppComponent appComponent) { public void setUp() { TeamCityApplication app = (TeamCityApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); app.getAppInjector().sharedUserStorage().clearAll(); - when(mClientBase.newCall(Matchers.any(Request.class))).thenReturn(mCall); + when(okHttpClient.newCall(Matchers.any(Request.class))).thenReturn(mCall); + when(unsafeOkHttpClient.newCall(Matchers.any(Request.class))).thenReturn(mCall); mActivityRule.launchActivity(null); } @@ -158,6 +164,47 @@ public Object answer(InvocationOnMock invocation) throws Throwable { SharedUserStorage storageUtils = app.getRestApiInjector().sharedUserStorage(); assertThat(storageUtils.hasGuestAccountWithUrl(savedUrl), is(true)); assertThat(storageUtils.getActiveUser().getTeamcityUrl(), is(savedUrl)); + assertThat(storageUtils.getActiveUser().isSslDisabled(), is(false)); + } + + /** + * Verifies that user can be logged in as guest user with correct account url ignoring ssl + */ + @Test + public void testUserCanCreateGuestUserAccountWithCorrectUrlIgnoringSsl() { + final String urlWithPath = "https://teamcity.com/server"; + String savedUrl = urlWithPath.concat("/"); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + mCallbackArgumentCaptor.getValue().onResponse( + mCall, + new Response.Builder() + .request(new Request.Builder().url(urlWithPath).build()) + .protocol(Protocol.HTTP_1_0) + .message(MESSAGE_EMPTY) + .code(200) + .build()); + return null; + } + }).when(mCall).enqueue(mCallbackArgumentCaptor.capture()); + + onView(withId(R.id.teamcity_url)).perform(typeText(urlWithPath.replace("https://", "")), closeSoftKeyboard()); + onView(withId(R.id.guest_user_switch)).perform(click()); + onView(withId(R.id.disable_ssl_switch)).perform(click()); + onView(withText(R.string.warning_ssl_dialog_content)).check(matches(isDisplayed())); + onView(withText(R.string.dialog_ok_title)).perform(click()); + onView(withId(R.id.btn_login)).perform(click()); + + intended(allOf( + hasComponent(RootProjectsActivity.class.getName()), + hasExtras(hasEntry(equalTo(BundleExtractorValues.IS_NEW_ACCOUNT_CREATED), equalTo(true))))); + + TeamCityApplication app = (TeamCityApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); + SharedUserStorage storageUtils = app.getRestApiInjector().sharedUserStorage(); + assertThat(storageUtils.hasGuestAccountWithUrl(savedUrl), is(true)); + assertThat(storageUtils.getActiveUser().getTeamcityUrl(), is(savedUrl)); + assertThat(storageUtils.getActiveUser().isSslDisabled(), is(true)); } /** diff --git a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/root/view/RootProjectsActivityTest.java b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/root/view/RootProjectsActivityTest.java index 778daf64..11d5a29f 100644 --- a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/root/view/RootProjectsActivityTest.java +++ b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/root/view/RootProjectsActivityTest.java @@ -91,7 +91,7 @@ public static void disableOnboarding() { public void setUp() { TeamCityApplication app = (TeamCityApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); app.getAppInjector().sharedUserStorage().clearAll(); - app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL); + app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL, false); } @Test diff --git a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/runbuild/view/RunBuildActivityTest.java b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/runbuild/view/RunBuildActivityTest.java index 54e044a0..7112b822 100644 --- a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/runbuild/view/RunBuildActivityTest.java +++ b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/runbuild/view/RunBuildActivityTest.java @@ -118,7 +118,7 @@ public void setComponent(RestApiComponent restApiComponent) { public void setUp() { TeamCityApplication app = (TeamCityApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); app.getRestApiInjector().sharedUserStorage().clearAll(); - app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL); + app.getRestApiInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL, false); } @Test diff --git a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/splash/view/SplashActivityTest.java b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/splash/view/SplashActivityTest.java index bca1807f..a28832a5 100644 --- a/app/src/androidTest/java/com/github/vase4kin/teamcityapp/splash/view/SplashActivityTest.java +++ b/app/src/androidTest/java/com/github/vase4kin/teamcityapp/splash/view/SplashActivityTest.java @@ -79,7 +79,7 @@ public void setUp() { public void testUserNavigatesToRootProjectsActivityIgnored() throws Exception { // Prepate data TeamCityApplication app = (TeamCityApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); - app.getAppInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL); + app.getAppInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL, false); // Launch activity mActivityRule.launchActivity(null); @@ -112,7 +112,7 @@ public void testUserNavigatesToLoginActivityIgnored() throws Exception { public void testUserNavigatesToRootProjectsActivity() throws Exception { // Prepate data TeamCityApplication app = (TeamCityApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); - app.getAppInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL); + app.getAppInjector().sharedUserStorage().saveGuestUserAccountAndSetItAsActive(Mocks.URL, false); // Launch activity mActivityRule.launchActivity(null); diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/dagger/CreateAccountModule.java b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/dagger/CreateAccountModule.java index dc42b8d6..575bacfb 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/dagger/CreateAccountModule.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/dagger/CreateAccountModule.java @@ -45,6 +45,7 @@ import okhttp3.OkHttpClient; import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_BASE; +import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_BASE_UNSAFE; @Module public class CreateAccountModule { @@ -63,9 +64,10 @@ CreateAccountView providesCreateAccountView() { @Provides CreateAccountDataManager providesCreateAccountDataManager(Context context, @Named(CLIENT_BASE) OkHttpClient okHttpClient, + @Named(CLIENT_BASE_UNSAFE) OkHttpClient unsafeOkHttpClient, SharedUserStorage sharedUserStorage, UrlFormatter urlFormatter) { - return new CreateAccountDataManagerImpl(context, okHttpClient, sharedUserStorage, urlFormatter); + return new CreateAccountDataManagerImpl(context, okHttpClient, unsafeOkHttpClient, sharedUserStorage, urlFormatter); } @Provides diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/data/CreateAccountDataManager.java b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/data/CreateAccountDataManager.java index c0e1bd52..06ce6da7 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/data/CreateAccountDataManager.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/data/CreateAccountDataManager.java @@ -30,16 +30,24 @@ public interface CreateAccountDataManager { * @param url - TeamCity server url * @param userName - Username * @param password - Password + * @param isSslDisabled - ssl state */ - void authUser(@NonNull CustomOnLoadingListener listener, String url, String userName, String password); + void authUser(@NonNull CustomOnLoadingListener listener, + String url, + String userName, + String password, + boolean isSslDisabled); /** * Server guest auth * * @param listener - to receive callbacks on {@link com.github.vase4kin.teamcityapp.account.create.presenter.CreateAccountPresenterImpl} * @param url - TeamCity server url + * @param isSslDisabled - ssl state */ - void authGuestUser(@NonNull CustomOnLoadingListener listener, String url); + void authGuestUser(@NonNull CustomOnLoadingListener listener, + String url, + boolean isSslDisabled); /** * Save user account in the local storage @@ -47,16 +55,22 @@ public interface CreateAccountDataManager { * @param url - TeamCity server url * @param userName - User name * @param password - Password + * @param isSslDisabled - ssl state * @param listener to receive data save callbacksĀ§ */ - void saveNewUserAccount(String serverUrl, String userName, String password, OnLoadingListener listener); + void saveNewUserAccount(String serverUrl, + String userName, + String password, + boolean isSslDisabled, + OnLoadingListener listener); /** * Save guest user account in the local storage * * @param url - TeamCity server url + * @param isSslDisabled - ssl state */ - void saveGuestUserAccount(String url); + void saveGuestUserAccount(String url, boolean isSslDisabled); /** * Init {@link com.github.vase4kin.teamcityapp.dagger.components.RestApiComponent} diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/data/CreateAccountDataManagerImpl.java b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/data/CreateAccountDataManagerImpl.java index e72d7a10..f4fb7b0c 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/data/CreateAccountDataManagerImpl.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/data/CreateAccountDataManagerImpl.java @@ -52,17 +52,20 @@ public class CreateAccountDataManagerImpl implements CreateAccountDataManager { */ private static final int DEFAULT_ERROR_CODE = 0; - private Context mContext; - private OkHttpClient mOkHttpClient; - private SharedUserStorage mSharedUserStorage; - private UrlFormatter mUrlFormatter; + private final Context mContext; + private final OkHttpClient baseOkHttpClient; + private final OkHttpClient unsafeBaseOkHttpClient; + private final SharedUserStorage mSharedUserStorage; + private final UrlFormatter mUrlFormatter; public CreateAccountDataManagerImpl(Context context, - OkHttpClient okHttpClient, + OkHttpClient baseOkHttpClient, + OkHttpClient unsafeBaseOkHttpClient, SharedUserStorage sharedUserStorage, UrlFormatter urlFormatter) { this.mContext = context; - this.mOkHttpClient = okHttpClient; + this.baseOkHttpClient = baseOkHttpClient; + this.unsafeBaseOkHttpClient = unsafeBaseOkHttpClient; this.mSharedUserStorage = sharedUserStorage; this.mUrlFormatter = urlFormatter; } @@ -74,9 +77,10 @@ public CreateAccountDataManagerImpl(Context context, public void authUser(@NonNull final CustomOnLoadingListener listener, final String url, final String userName, - final String password) { + final String password, + boolean isSslDisabled) { // Creating okHttpClient with authenticator - OkHttpClient okHttpClient = mOkHttpClient.newBuilder().authenticator(new Authenticator() { + OkHttpClient okHttpClient = getClient(isSslDisabled).newBuilder().authenticator(new Authenticator() { @Override public Request authenticate(Route route, Response response) throws IOException { String credential = Credentials.basic(userName, password); @@ -98,8 +102,17 @@ public Request authenticate(Route route, Response response) throws IOException { * {@inheritDoc} */ @Override - public void authGuestUser(@NonNull final CustomOnLoadingListener listener, final String url) { - handleAuthRequest(url, AUTH_GUEST_URL, mOkHttpClient, listener); + public void authGuestUser(@NonNull final CustomOnLoadingListener listener, + final String url, + boolean isSslDisabled) { + handleAuthRequest(url, AUTH_GUEST_URL, getClient(isSslDisabled), listener); + } + + /** + * @return {@link OkHttpClient} depending on ssl enabled state + */ + private OkHttpClient getClient(boolean isSslEnabled) { + return isSslEnabled ? unsafeBaseOkHttpClient : baseOkHttpClient; } /** @@ -152,7 +165,17 @@ public void run() { String formattedServerUrl = mUrlFormatter.formatServerUrl(serverUrl); listener.onSuccess(formattedServerUrl); } else { - listener.onFail(response.code(), response.message()); + String message; + if (response.body() != null && response.body().source() != null) { + try { + message = response.body().source().readUtf8(); + } catch (IOException e) { + message = response.message(); + } + } else { + message = response.message(); + } + listener.onFail(response.code(), message); } } }); @@ -169,8 +192,12 @@ public void run() { * {@inheritDoc} */ @Override - public void saveNewUserAccount(final String serverUrl, String userName, String password, final OnLoadingListener listener) { - mSharedUserStorage.saveUserAccountAndSetItAsActive(serverUrl, userName, password, new SharedUserStorage.OnStorageListener() { + public void saveNewUserAccount(final String serverUrl, + String userName, + String password, + boolean isSslDisabled, + final OnLoadingListener listener) { + mSharedUserStorage.saveUserAccountAndSetItAsActive(serverUrl, userName, password, isSslDisabled, new SharedUserStorage.OnStorageListener() { @Override public void onSuccess() { // On data save success @@ -189,8 +216,8 @@ public void onFail() { * {@inheritDoc} */ @Override - public void saveGuestUserAccount(String url) { - mSharedUserStorage.saveGuestUserAccountAndSetItAsActive(url); + public void saveGuestUserAccount(String url, boolean isSslDisabled) { + mSharedUserStorage.saveGuestUserAccountAndSetItAsActive(url, isSslDisabled); } /** diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/presenter/CreateAccountPresenterImpl.java b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/presenter/CreateAccountPresenterImpl.java index 2bcd44c4..8d1eaf93 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/presenter/CreateAccountPresenterImpl.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/presenter/CreateAccountPresenterImpl.java @@ -25,20 +25,19 @@ import com.github.vase4kin.teamcityapp.account.create.router.CreateAccountRouter; import com.github.vase4kin.teamcityapp.account.create.tracker.CreateAccountTracker; import com.github.vase4kin.teamcityapp.account.create.view.CreateAccountView; -import com.github.vase4kin.teamcityapp.account.create.view.OnCreateAccountPresenterListener; import javax.inject.Inject; /** * Impl of {@link CreateAccountPresenter} */ -public class CreateAccountPresenterImpl implements CreateAccountPresenter, OnCreateAccountPresenterListener, OnLoadingListener { +public class CreateAccountPresenterImpl implements CreateAccountPresenter, CreateAccountView.ViewListener { - private CreateAccountView mView; - private CreateAccountDataManager mDataManager; - private CreateAccountDataModel mDataModel; - private CreateAccountRouter mRouter; - private CreateAccountTracker mTracker; + private final CreateAccountView view; + private final CreateAccountDataManager dataManager; + private final CreateAccountDataModel dataModel; + private final CreateAccountRouter router; + private final CreateAccountTracker tracker; @Inject CreateAccountPresenterImpl(CreateAccountView view, @@ -46,11 +45,11 @@ public class CreateAccountPresenterImpl implements CreateAccountPresenter, OnCre CreateAccountDataModel dataModel, CreateAccountRouter router, CreateAccountTracker tracker) { - this.mView = view; - this.mDataManager = dataManager; - this.mDataModel = dataModel; - this.mRouter = router; - this.mTracker = tracker; + this.view = view; + this.dataManager = dataManager; + this.dataModel = dataModel; + this.router = router; + this.tracker = tracker; } /** @@ -58,7 +57,7 @@ public class CreateAccountPresenterImpl implements CreateAccountPresenter, OnCre */ @Override public void handleOnCreateView() { - mView.initViews(this); + view.initViews(this); } /** @@ -66,7 +65,7 @@ public void handleOnCreateView() { */ @Override public void handleOnResume() { - mTracker.trackView(); + tracker.trackView(); } /** @@ -74,107 +73,97 @@ public void handleOnResume() { */ @Override public void handleOnDestroy() { - mView.onDestroyView(); + view.onDestroyView(); } /** * {@inheritDoc} */ @Override - public void validateUserData(String url, final String userName, final String password) { - mView.hideError(); + public void validateUserData(String url, final String userName, final String password, final boolean isSslDisabled) { + view.hideError(); if (TextUtils.isEmpty(url)) { - mView.showServerUrlCanNotBeEmptyError(); + view.showServerUrlCanNotBeEmptyError(); return; } if (TextUtils.isEmpty(userName)) { - mView.showUserNameCanNotBeEmptyError(); + view.showUserNameCanNotBeEmptyError(); return; } if (TextUtils.isEmpty(password)) { - mView.showPasswordCanNotBeEmptyError(); + view.showPasswordCanNotBeEmptyError(); return; } - mView.showProgressDialog(); - if (mDataModel.hasAccountWithUrl(url, userName)) { - mView.showNewAccountExistErrorMessage(); - mView.dismissProgressDialog(); + view.showProgressDialog(); + if (dataModel.hasAccountWithUrl(url, userName)) { + view.showNewAccountExistErrorMessage(); + view.dismissProgressDialog(); } else { - mDataManager.authUser(new CustomOnLoadingListener() { + dataManager.authUser(new CustomOnLoadingListener() { @Override public void onSuccess(String url) { - mDataManager.saveNewUserAccount(url, userName, password, CreateAccountPresenterImpl.this); + dataManager.saveNewUserAccount(url, userName, password, false, new OnLoadingListener() { + @Override + public void onSuccess(String serverUrl) { + dataManager.initTeamCityService(serverUrl); + tracker.trackUserLoginSuccess(!isSslDisabled); + view.dismissProgressDialog(); + view.finish(); + router.startRootProjectActivityWhenNewAccountIsCreated(); + } + + @Override + public void onFail(String errorMessage) { + view.showCouldNotSaveUserError(); + view.dismissProgressDialog(); + tracker.trackUserDataSaveFailed(); + } + }); } @Override public void onFail(int code, String errorMessage) { - mView.showError(errorMessage); - mView.dismissProgressDialog(); - mTracker.trackUserLoginFailed(errorMessage); + view.showError(errorMessage); + view.dismissProgressDialog(); + tracker.trackUserLoginFailed(errorMessage); } - }, url, userName, password); + }, url, userName, password, isSslDisabled); } } - /** - * On data save success callback - * - * @param serverUrl - Server url - */ - @Override - public void onSuccess(String serverUrl) { - mDataManager.initTeamCityService(serverUrl); - mTracker.trackUserLoginSuccess(); - mView.dismissProgressDialog(); - mView.finish(); - mRouter.startRootProjectActivityWhenNewAccountIsCreated(); - } - - /** - * On data save fail callback - * - * @param errorMessage - Error message - */ - @Override - public void onFail(String errorMessage) { - mView.showCouldNotSaveUserError(); - mView.dismissProgressDialog(); - mTracker.trackUserDataSaveFailed(); - } - /** * {@inheritDoc} */ @Override - public void validateGuestUserData(String url) { - mView.hideError(); + public void validateGuestUserData(String url, final boolean isSslDisabled) { + view.hideError(); if (TextUtils.isEmpty(url)) { - mView.showServerUrlCanNotBeEmptyError(); + view.showServerUrlCanNotBeEmptyError(); return; } - mView.showProgressDialog(); - if (mDataModel.hasGuestAccountWithUrl(url)) { - mView.showNewAccountExistErrorMessage(); - mView.dismissProgressDialog(); + view.showProgressDialog(); + if (dataModel.hasGuestAccountWithUrl(url)) { + view.showNewAccountExistErrorMessage(); + view.dismissProgressDialog(); } else { - mDataManager.authGuestUser(new CustomOnLoadingListener() { + dataManager.authGuestUser(new CustomOnLoadingListener() { @Override public void onSuccess(String url) { - mDataManager.saveGuestUserAccount(url); - mDataManager.initTeamCityService(url); - mTracker.trackGuestUserLoginSuccess(); - mView.dismissProgressDialog(); - mView.finish(); - mRouter.startRootProjectActivityWhenNewAccountIsCreated(); + dataManager.saveGuestUserAccount(url, isSslDisabled); + dataManager.initTeamCityService(url); + tracker.trackGuestUserLoginSuccess(!isSslDisabled); + view.dismissProgressDialog(); + view.finish(); + router.startRootProjectActivityWhenNewAccountIsCreated(); } @Override public void onFail(int code, String errorMessage) { - mView.showError(errorMessage); - mView.dismissProgressDialog(); - mTracker.trackGuestUserLoginFailed(errorMessage); + view.showError(errorMessage); + view.dismissProgressDialog(); + tracker.trackGuestUserLoginFailed(errorMessage); } - }, url); + }, url, isSslDisabled); } } @@ -184,10 +173,10 @@ public void onFail(int code, String errorMessage) { @Override public void finish() { // If guest user account enabled or not - if (!mView.isEmailEmpty()) { - mView.showDiscardDialog(); + if (!view.isEmailEmpty()) { + view.showDiscardDialog(); } else { - mView.finish(); + view.finish(); } } @@ -198,4 +187,12 @@ public void finish() { public void onClick() { finish(); } + + /** + * {@inheritDoc} + */ + @Override + public void onDisableSslSwitchClick() { + view.showDisableSslWarningDialog(); + } } diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/CreateAccountTracker.java b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/CreateAccountTracker.java index 93a96ff9..f90d03b5 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/CreateAccountTracker.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/CreateAccountTracker.java @@ -28,6 +28,11 @@ public interface CreateAccountTracker extends ViewTracker { */ String ATTRIBUTE_NAME_ERROR = "errorMessage"; + /** + * Ssl enabled attribute + */ + String ATTRIBUTE_NAME_SSL_ENABLED = "sslEnabled"; + /** * Error message if data is failed to save */ @@ -40,13 +45,15 @@ public interface CreateAccountTracker extends ViewTracker { /** * Track user is logged in + * @param isSslEnabled */ - void trackUserLoginSuccess(); + void trackUserLoginSuccess(boolean isSslEnabled); /** * Track guest user is logged in + * @param isSslEnabled */ - void trackGuestUserLoginSuccess(); + void trackGuestUserLoginSuccess(boolean isSslEnabled); /** * Track user is failed to login diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/CreateAccountTrackerImpl.java b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/CreateAccountTrackerImpl.java index ca3fd841..93a53b6f 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/CreateAccountTrackerImpl.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/CreateAccountTrackerImpl.java @@ -33,11 +33,11 @@ public CreateAccountTrackerImpl(Set trackers) { * {@inheritDoc} */ @Override - public void trackUserLoginSuccess() { + public void trackUserLoginSuccess(final boolean isSslEnabled) { logEvent(new TrackerMethod() { @Override public void execute(CreateAccountTracker tracker) { - tracker.trackUserLoginSuccess(); + tracker.trackUserLoginSuccess(isSslEnabled); } }); } @@ -46,11 +46,11 @@ public void execute(CreateAccountTracker tracker) { * {@inheritDoc} */ @Override - public void trackGuestUserLoginSuccess() { + public void trackGuestUserLoginSuccess(final boolean isSslEnabled) { logEvent(new TrackerMethod() { @Override public void execute(CreateAccountTracker tracker) { - tracker.trackGuestUserLoginSuccess(); + tracker.trackGuestUserLoginSuccess(isSslEnabled); } }); } diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/FabricCreateAccountTrackerImpl.java b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/FabricCreateAccountTrackerImpl.java index ffa718a3..2a02e169 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/FabricCreateAccountTrackerImpl.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/FabricCreateAccountTrackerImpl.java @@ -40,15 +40,18 @@ public class FabricCreateAccountTrackerImpl implements CreateAccountTracker { * {@inheritDoc} */ @Override - public void trackUserLoginSuccess() { + public void trackUserLoginSuccess(boolean isSslEnabled) { if (!Fabric.isInitialized()) return; Answers.getInstance().logLogin(new LoginEvent() .putMethod(METHOD_USER_LOGIN) .putSuccess(true)); } + /** + * {@inheritDoc} + */ @Override - public void trackGuestUserLoginSuccess() { + public void trackGuestUserLoginSuccess(boolean isSslEnabled) { if (!Fabric.isInitialized()) return; Answers.getInstance().logLogin(new LoginEvent() .putMethod(METHOD_GUEST_USER_LOGIN) @@ -76,6 +79,9 @@ public void trackGuestUserLoginFailed(String errorMessage) { .putCustomAttribute(ATTRIBUTE_NAME_ERROR, errorMessage)); } + /** + * {@inheritDoc} + */ @Override public void trackUserDataSaveFailed() { trackUserLoginFailed(MESSAGE_ERROR_SAVE_DATE); diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/FirebaseCreateAccountTrackerImpl.java b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/FirebaseCreateAccountTrackerImpl.java index 3b8edaa2..19882938 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/FirebaseCreateAccountTrackerImpl.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/tracker/FirebaseCreateAccountTrackerImpl.java @@ -48,13 +48,20 @@ public FirebaseCreateAccountTrackerImpl(FirebaseAnalytics firebaseAnalytics) { * {@inheritDoc} */ @Override - public void trackUserLoginSuccess() { - mFirebaseAnalytics.logEvent(EVENT_LOGIN_USER_SUCCESS, null); + public void trackUserLoginSuccess(boolean isSslEnabled) { + Bundle bundle = new Bundle(); + bundle.putBoolean(ATTRIBUTE_NAME_SSL_ENABLED, isSslEnabled); + mFirebaseAnalytics.logEvent(EVENT_LOGIN_USER_SUCCESS, bundle); } + /** + * {@inheritDoc} + */ @Override - public void trackGuestUserLoginSuccess() { - mFirebaseAnalytics.logEvent(EVENT_LOGIN_GUEST_USER_SUCCESS, null); + public void trackGuestUserLoginSuccess(boolean isSslEnabled) { + Bundle bundle = new Bundle(); + bundle.putBoolean(ATTRIBUTE_NAME_SSL_ENABLED, isSslEnabled); + mFirebaseAnalytics.logEvent(EVENT_LOGIN_GUEST_USER_SUCCESS, bundle); } /** @@ -67,6 +74,9 @@ public void trackUserLoginFailed(String errorMessage) { mFirebaseAnalytics.logEvent(EVENT_LOGIN_USER_FAILED, bundle); } + /** + * {@inheritDoc} + */ @Override public void trackGuestUserLoginFailed(String errorMessage) { Bundle bundle = new Bundle(); @@ -74,6 +84,9 @@ public void trackGuestUserLoginFailed(String errorMessage) { mFirebaseAnalytics.logEvent(EVENT_LOGIN_GUEST_USER_FAILED, bundle); } + /** + * {@inheritDoc} + */ @Override public void trackUserDataSaveFailed() { trackUserLoginFailed(MESSAGE_ERROR_SAVE_DATE); diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/CreateAccountView.java b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/CreateAccountView.java index 8bfc6de7..045656f5 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/CreateAccountView.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/CreateAccountView.java @@ -26,7 +26,7 @@ public interface CreateAccountView { * * @param listener - Listener to receive callback on {@link com.github.vase4kin.teamcityapp.account.create.presenter.CreateAccountPresenterImpl} */ - void initViews(OnCreateAccountPresenterListener listener); + void initViews(ViewListener listener); /** * Set error text for text field @@ -96,4 +96,20 @@ public interface CreateAccountView { * Show error message that account exists */ void showNewAccountExistErrorMessage(); + + /** + * Show warning dialog about disabling ssl + */ + void showDisableSslWarningDialog(); + + /** + * On create account listener + */ + interface ViewListener extends OnValidateListener, OnToolBarNavigationListener { + + /** + * On ignore ssl switch click + */ + void onDisableSslSwitchClick(); + } } diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/CreateAccountViewImpl.java b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/CreateAccountViewImpl.java index d7d43d6c..ca8460c8 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/CreateAccountViewImpl.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/CreateAccountViewImpl.java @@ -18,6 +18,7 @@ import android.app.Activity; import android.graphics.Color; +import android.support.annotation.NonNull; import android.support.design.widget.TextInputLayout; import android.support.v7.widget.Toolbar; import android.text.TextUtils; @@ -29,6 +30,7 @@ import android.widget.Switch; import android.widget.TextView; +import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.github.vase4kin.teamcityapp.R; import com.joanzapata.iconify.IconDrawable; @@ -77,6 +79,9 @@ public class CreateAccountViewImpl implements CreateAccountView { @BindView(R.id.guest_user_switch) Switch mGuestUserSwitch; + @BindView(R.id.disable_ssl_switch) + Switch disableSslSwitch; + private Unbinder mUnbinder; private Activity mActivity; @@ -91,7 +96,7 @@ public CreateAccountViewImpl(Activity activity) { * {@inheritDoc} */ @Override - public void initViews(final OnCreateAccountPresenterListener listener) { + public void initViews(final ViewListener listener) { mUnbinder = ButterKnife.bind(this, mActivity); initDialogs(); initToolbar(listener); @@ -103,7 +108,8 @@ public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent listener.validateUserData( mServerUrl.getText().toString().trim(), mUserName.getText().toString().trim(), - mPassword.getText().toString().trim()); + mPassword.getText().toString().trim(), + disableSslSwitch.isChecked()); return true; } return false; @@ -125,6 +131,15 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean b) { //Set text selection to the end mServerUrl.setSelection(mServerUrl.getText().length()); + + disableSslSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + listener.onDisableSslSwitchClick(); + } + } + }); } /** @@ -143,12 +158,13 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE) { if (mGuestUserSwitch.isChecked()) { listener.validateGuestUserData( - mServerUrl.getText().toString().trim()); + mServerUrl.getText().toString().trim(), disableSslSwitch.isChecked()); } else { listener.validateUserData( mServerUrl.getText().toString().trim(), mUserName.getText().toString().trim(), - mPassword.getText().toString().trim()); + mPassword.getText().toString().trim(), + disableSslSwitch.isChecked()); } } return false; @@ -271,14 +287,14 @@ public void showNewAccountExistErrorMessage() { * * @param listener - Listener to receive callbacks */ - private void initToolbar(final OnCreateAccountPresenterListener listener) { + private void initToolbar(final ViewListener listener) { mToolbar.setTitle(R.string.add_new_account_dialog_title); // For ui testing purpose mToolbar.setNavigationContentDescription(R.string.navigate_up); mToolbar.setNavigationIcon(new IconDrawable(mActivity, MaterialIcons.md_close).color(Color.WHITE).actionBarSize()); mToolbar.setNavigationOnClickListener(new OnToolBarNavigationListenerImpl(listener)); mToolbar.inflateMenu(R.menu.menu_create_account_dialog); - mToolbar.setOnMenuItemClickListener(new OnCreateMenuItemClickListenerImpl(listener, mServerUrl, mUserName, mPassword, mGuestUserSwitch)); + mToolbar.setOnMenuItemClickListener(new OnCreateMenuItemClickListenerImpl(listener, mServerUrl, mUserName, mPassword, mGuestUserSwitch, disableSslSwitch)); } /** @@ -317,4 +333,41 @@ public void onPositive(MaterialDialog dialog) { .negativeColor(mOrangeColor) .build(); } + + /** + * {@inheritDoc} + */ + @Override + public void showDisableSslWarningDialog() { + new MaterialDialog.Builder(mActivity) + .titleColor(mWhiteColor) + .title(R.string.warning_ssl_dialog_title) + .content(R.string.warning_ssl_dialog_content) + .widgetColor(mWhiteColor) + .contentColor(mWhiteColor) + .backgroundColor(mPrimaryColor) + .positiveColor(mOrangeColor) + .positiveText(R.string.dialog_ok_title) + .negativeColor(mOrangeColor) + .negativeText(R.string.warning_ssl_dialog_negative) + .linkColor(mOrangeColor) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + disableSslSwitch.setChecked(true); + dialog.dismiss(); + } + }) + .onNegative(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + disableSslSwitch.setChecked(false); + dialog.dismiss(); + } + }) + .canceledOnTouchOutside(false) + .autoDismiss(false) + .cancelable(false) + .show(); + } } diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/OnCreateMenuItemClickListenerImpl.java b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/OnCreateMenuItemClickListenerImpl.java index a52b3150..53968ddc 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/OnCreateMenuItemClickListenerImpl.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/OnCreateMenuItemClickListenerImpl.java @@ -25,35 +25,40 @@ public class OnCreateMenuItemClickListenerImpl implements Toolbar.OnMenuItemClickListener { - private OnValidateListener mOnValidateListener; - private TextView mServerUrl; - private TextView mUserName; - private TextView mPassword; - private Switch mGuestUserSwitch; + private final OnValidateListener onValidateListener; + private final TextView serverUrl; + private final TextView userName; + private final TextView password; + private final Switch guestUserSwitch; + private final Switch disableSslSwitch; - public OnCreateMenuItemClickListenerImpl(OnValidateListener mOnValidateListener, - TextView serverUrl, - TextView userName, - TextView password, - Switch guestUserSwitch) { - this.mOnValidateListener = mOnValidateListener; - this.mServerUrl = serverUrl; - this.mUserName = userName; - this.mPassword = password; - this.mGuestUserSwitch = guestUserSwitch; + OnCreateMenuItemClickListenerImpl(OnValidateListener onValidateListener, + TextView serverUrl, + TextView userName, + TextView password, + Switch guestUserSwitch, + Switch disableSslSwitch) { + this.onValidateListener = onValidateListener; + this.serverUrl = serverUrl; + this.userName = userName; + this.password = password; + this.guestUserSwitch = guestUserSwitch; + this.disableSslSwitch = disableSslSwitch; } @Override public boolean onMenuItemClick(MenuItem item) { if (item.getItemId() == R.id.action_create) { - if (mGuestUserSwitch.isChecked()) { - mOnValidateListener.validateGuestUserData( - mServerUrl.getText().toString().trim()); + if (guestUserSwitch.isChecked()) { + onValidateListener.validateGuestUserData( + serverUrl.getText().toString().trim(), + disableSslSwitch.isChecked()); } else { - mOnValidateListener.validateUserData( - mServerUrl.getText().toString().trim(), - mUserName.getText().toString().trim(), - mPassword.getText().toString().trim()); + onValidateListener.validateUserData( + serverUrl.getText().toString().trim(), + userName.getText().toString().trim(), + password.getText().toString().trim(), + disableSslSwitch.isChecked()); } return true; } diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/OnValidateListener.java b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/OnValidateListener.java index 69b48190..8110c939 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/OnValidateListener.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/OnValidateListener.java @@ -25,8 +25,9 @@ public interface OnValidateListener { * Validate guest user data * * @param url - TeamCity server url + * @param isSslDisabled - ssl state */ - void validateGuestUserData(String url); + void validateGuestUserData(String url, boolean isSslDisabled); /** * Validate user data @@ -34,6 +35,7 @@ public interface OnValidateListener { * @param url - TeamCity server url * @param userName - User name * @param password - User password + * @param isSslDisabled - ssl state */ - void validateUserData(String url, String userName, String password); + void validateUserData(String url, String userName, String password, boolean isSslDisabled); } diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/dagger/BuildLogInteractorModule.java b/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/dagger/BuildLogInteractorModule.java index b4ada5b2..3cf55fe3 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/dagger/BuildLogInteractorModule.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/dagger/BuildLogInteractorModule.java @@ -20,6 +20,8 @@ import com.github.vase4kin.teamcityapp.buildlog.data.BuildLogInteractor; import com.github.vase4kin.teamcityapp.buildlog.data.BuildLogInteractorImpl; +import com.github.vase4kin.teamcityapp.buildlog.view.BuildLogWebViewClient; +import com.github.vase4kin.teamcityapp.buildlog.view.UnsafeBuildLogWebViewClient; import com.github.vase4kin.teamcityapp.storage.SharedUserStorage; import dagger.Module; @@ -32,4 +34,11 @@ public class BuildLogInteractorModule { BuildLogInteractor providesBuildLogInteractor(Context context, SharedUserStorage sharedUserStorage) { return new BuildLogInteractorImpl(context, sharedUserStorage.getActiveUser()); } + + @Provides + BuildLogWebViewClient providesBuildLogWebViewClient(SharedUserStorage sharedUserStorage) { + return !sharedUserStorage.getActiveUser().isSslDisabled() + ? new BuildLogWebViewClient() + : new UnsafeBuildLogWebViewClient(); + } } diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/view/BuildLogViewImpl.java b/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/view/BuildLogViewImpl.java index dce76ab4..a6e95bff 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/view/BuildLogViewImpl.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/view/BuildLogViewImpl.java @@ -49,12 +49,12 @@ public class BuildLogViewImpl implements BuildLogView, OnBuildLogViewListener { private Unbinder mUnbinder; - private View mView; + private final View mView; + private final BuildLogWebViewClient client; - private BuildLogWebViewClient mClient; - - public BuildLogViewImpl(View mView) { + public BuildLogViewImpl(View mView, BuildLogWebViewClient client) { this.mView = mView; + this.client = client; } /** @@ -81,10 +81,9 @@ public void onClick(View v) { mWebView.getSettings().setJavaScriptEnabled(true); mWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); - mClient = new BuildLogWebViewClient(); - mClient.setListener(this); + client.setListener(this); - mWebView.setWebViewClient(mClient); + mWebView.setWebViewClient(client); } /** @@ -118,7 +117,7 @@ public void hideAuthView() { @Override public void unBindViews() { mUnbinder.unbind(); - mClient.setListener(null); + client.setListener(null); } /** diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/view/BuildLogWebViewClient.java b/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/view/BuildLogWebViewClient.java index 86c71eb1..c271031c 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/view/BuildLogWebViewClient.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/view/BuildLogWebViewClient.java @@ -28,7 +28,7 @@ /** * Build log web client with listener to receive client callbacks */ -class BuildLogWebViewClient extends WebViewClient { +public class BuildLogWebViewClient extends WebViewClient { /** * Magic awesome js script to hide things user doesn't need to see diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/OnCreateAccountPresenterListener.java b/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/view/UnsafeBuildLogWebViewClient.java similarity index 59% rename from app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/OnCreateAccountPresenterListener.java rename to app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/view/UnsafeBuildLogWebViewClient.java index 592f42f9..91d19213 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/account/create/view/OnCreateAccountPresenterListener.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/buildlog/view/UnsafeBuildLogWebViewClient.java @@ -14,10 +14,19 @@ * limitations under the License. */ -package com.github.vase4kin.teamcityapp.account.create.view; +package com.github.vase4kin.teamcityapp.buildlog.view; + +import android.net.http.SslError; +import android.webkit.SslErrorHandler; +import android.webkit.WebView; /** - * On create account listener + * Unsafe build log web view client */ -public interface OnCreateAccountPresenterListener extends OnValidateListener, OnToolBarNavigationListener { +public class UnsafeBuildLogWebViewClient extends BuildLogWebViewClient { + + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + handler.proceed(); + } } diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/dagger/components/AppComponent.java b/app/src/main/java/com/github/vase4kin/teamcityapp/dagger/components/AppComponent.java index 5a7a6660..0eb02f07 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/dagger/components/AppComponent.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/dagger/components/AppComponent.java @@ -35,6 +35,7 @@ import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_AUTH; import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_BASE; +import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_BASE_UNSAFE; @Singleton @Component(modules = AppModule.class) @@ -47,6 +48,9 @@ public interface AppComponent { @Named(CLIENT_BASE) OkHttpClient baseOkHttpClient(); + @Named(CLIENT_BASE_UNSAFE) + OkHttpClient unsafeBaseOkHttpClient(); + @Named(CLIENT_AUTH) OkHttpClient authOkHttpClient(); diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/dagger/modules/AppModule.java b/app/src/main/java/com/github/vase4kin/teamcityapp/dagger/modules/AppModule.java index 8fb9b57f..8fd9f8ad 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/dagger/modules/AppModule.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/dagger/modules/AppModule.java @@ -44,6 +44,12 @@ import javax.inject.Named; import javax.inject.Singleton; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; import dagger.Module; import dagger.Provides; @@ -60,6 +66,7 @@ public class AppModule { private static final int WRITE_TIMEOUT = 10; public static final String CLIENT_BASE = "base"; + public static final String CLIENT_BASE_UNSAFE = "base_unsafe"; public static final String CLIENT_AUTH = "auth"; private TeamCityApplication mApplication; @@ -86,19 +93,63 @@ protected SharedUserStorage provideSharedUserStorage(CryptoManager cryptoManager @Named(CLIENT_AUTH) @Provides protected OkHttpClient providesAuthHttpClient(SharedUserStorage sharedUserStorage, - @Named(CLIENT_BASE) OkHttpClient okHttpClient) { + @Named(CLIENT_BASE) OkHttpClient baseOkHttpClient, + @Named(CLIENT_BASE_UNSAFE) OkHttpClient unsafeBaseOkHttpClient) { UserAccount userAccount = sharedUserStorage.getActiveUser(); + OkHttpClient client = userAccount.isSslDisabled() ? unsafeBaseOkHttpClient : baseOkHttpClient; if (userAccount.isGuestUser()) { - return okHttpClient.newBuilder() + return client.newBuilder() .addInterceptor(new GuestUserAuthInterceptor()) .build(); } else { - return okHttpClient.newBuilder() + return client.newBuilder() .authenticator(new TeamCityAuthenticator(userAccount)) .build(); } } + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + @Named(CLIENT_BASE_UNSAFE) + @Singleton + @Provides + protected OkHttpClient providesUnsafeBaseHttpClient(@Named(CLIENT_BASE) OkHttpClient baseOkHttpClient) { + final TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) { + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[]{}; + } + } + }; + + // Install the all-trusting trust manager + try { + final SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + // Create an ssl socket factory with our all-trusting manager + final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + return baseOkHttpClient.newBuilder() + .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]) + .hostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }) + .build(); + } catch (Exception e) { + return baseOkHttpClient; + } + } + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) @Named(CLIENT_BASE) @Singleton diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/login/dagger/LoginModule.java b/app/src/main/java/com/github/vase4kin/teamcityapp/login/dagger/LoginModule.java index 1c16c389..38764327 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/login/dagger/LoginModule.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/login/dagger/LoginModule.java @@ -43,6 +43,7 @@ import okhttp3.OkHttpClient; import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_BASE; +import static com.github.vase4kin.teamcityapp.dagger.modules.AppModule.CLIENT_BASE_UNSAFE; @Module public class LoginModule { @@ -60,10 +61,12 @@ LoginView providesLoginView() { @Provides CreateAccountDataManager providesCreateAccountDataManager(Context context, - @Named(CLIENT_BASE) OkHttpClient okHttpClient, + @Named(CLIENT_BASE) OkHttpClient baseOkHttpClient, + @Named(CLIENT_BASE_UNSAFE) OkHttpClient unsafeBaseOkHttpClient, SharedUserStorage sharedUserStorage, UrlFormatter urlFormatter) { - return new CreateAccountDataManagerImpl(context, okHttpClient, sharedUserStorage, urlFormatter); + return new CreateAccountDataManagerImpl( + context, baseOkHttpClient, unsafeBaseOkHttpClient, sharedUserStorage, urlFormatter); } @Provides diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/login/presenter/LoginPresenterImpl.java b/app/src/main/java/com/github/vase4kin/teamcityapp/login/presenter/LoginPresenterImpl.java index 2825d8b4..69431acb 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/login/presenter/LoginPresenterImpl.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/login/presenter/LoginPresenterImpl.java @@ -25,31 +25,30 @@ import com.github.vase4kin.teamcityapp.login.router.LoginRouter; import com.github.vase4kin.teamcityapp.login.tracker.LoginTracker; import com.github.vase4kin.teamcityapp.login.view.LoginView; -import com.github.vase4kin.teamcityapp.login.view.OnLoginButtonClickListener; import javax.inject.Inject; /** * Impl for {@link LoginPresenter} */ -public class LoginPresenterImpl implements LoginPresenter, OnLoginButtonClickListener, OnLoadingListener { +public class LoginPresenterImpl implements LoginPresenter, LoginView.ViewListener { private static final int UNAUTHORIZED_STATUS_CODE = 401; - private LoginView mView; - private CreateAccountDataManager mDataManager; - private LoginRouter mRouter; - private LoginTracker mTracker; + private final LoginView view; + private final CreateAccountDataManager dataManager; + private final LoginRouter router; + private final LoginTracker tracker; @Inject - LoginPresenterImpl(@NonNull LoginView mView, - @NonNull CreateAccountDataManager mDataManager, - @NonNull LoginRouter mRouter, + LoginPresenterImpl(@NonNull LoginView view, + @NonNull CreateAccountDataManager dataManager, + @NonNull LoginRouter router, @NonNull LoginTracker tracker) { - this.mView = mView; - this.mDataManager = mDataManager; - this.mRouter = mRouter; - this.mTracker = tracker; + this.view = view; + this.dataManager = dataManager; + this.router = router; + this.tracker = tracker; } /** @@ -57,7 +56,7 @@ public class LoginPresenterImpl implements LoginPresenter, OnLoginButtonClickLis */ @Override public void onCreate() { - mView.initViews(this); + view.initViews(this); } /** @@ -65,7 +64,7 @@ public void onCreate() { */ @Override public void onDestroy() { - mView.unbindViews(); + view.unbindViews(); } /** @@ -73,7 +72,7 @@ public void onDestroy() { */ @Override public void onResume() { - mTracker.trackView(); + tracker.trackView(); } /** @@ -81,103 +80,104 @@ public void onResume() { */ @Override public void onWindowFocusChanged(boolean hasFocus) { - mView.onWindowFocusChanged(hasFocus); + view.onWindowFocusChanged(hasFocus); } /** * {@inheritDoc} */ @Override - public void onUserLoginButtonClick(String serverUrl, final String userName, final String password) { - mView.hideError(); + public void onUserLoginButtonClick(String serverUrl, + final String userName, + final String password, + final boolean isSslDisabled) { + view.hideError(); if (TextUtils.isEmpty(serverUrl)) { - mView.showServerUrlCanNotBeEmptyError(); + view.showServerUrlCanNotBeEmptyError(); return; } if (TextUtils.isEmpty(userName)) { - mView.showUserNameCanNotBeEmptyError(); + view.showUserNameCanNotBeEmptyError(); return; } if (TextUtils.isEmpty(password)) { - mView.showPasswordCanNotBeEmptyError(); + view.showPasswordCanNotBeEmptyError(); return; } - mView.showProgressDialog(); - mDataManager.authUser(new CustomOnLoadingListener() { + view.showProgressDialog(); + dataManager.authUser(new CustomOnLoadingListener() { @Override public void onSuccess(String serverUrl) { - mView.dismissProgressDialog(); - mDataManager.saveNewUserAccount(serverUrl, userName, password, LoginPresenterImpl.this); + view.dismissProgressDialog(); + dataManager.saveNewUserAccount(serverUrl, userName, password, isSslDisabled, new OnLoadingListener() { + @Override + public void onSuccess(String serverUrl) { + dataManager.initTeamCityService(serverUrl); + router.openProjectsRootPageForFirstStart(); + tracker.trackUserLoginSuccess(!isSslDisabled); + view.close(); + } + + @Override + public void onFail(String errorMessage) { + view.dismissProgressDialog(); + view.showCouldNotSaveUserError(); + tracker.trackUserDataSaveFailed(); + view.hideKeyboard(); + } + }); } @Override public void onFail(int statusCode, String errorMessage) { - mView.dismissProgressDialog(); - mView.showError(errorMessage); - mTracker.trackUserLoginFailed(errorMessage); - mView.hideKeyboard(); + view.dismissProgressDialog(); + view.showError(errorMessage); + tracker.trackUserLoginFailed(errorMessage); + view.hideKeyboard(); } - }, serverUrl, userName, password); - } - - /** - * On data save success callback - * - * @param serverUrl - Server url - */ - @Override - public void onSuccess(String serverUrl) { - mDataManager.initTeamCityService(serverUrl); - mRouter.openProjectsRootPageForFirstStart(); - mTracker.trackUserLoginSuccess(); - mView.close(); - } - - /** - * On data save fail callback - * - * @param errorMessage - Error message - */ - @Override - public void onFail(String errorMessage) { - mView.dismissProgressDialog(); - mView.showCouldNotSaveUserError(); - mTracker.trackUserDataSaveFailed(); - mView.hideKeyboard(); + }, serverUrl, userName, password, isSslDisabled); } /** * {@inheritDoc} */ @Override - public void onGuestUserLoginButtonClick(String serverUrl) { - mView.hideError(); + public void onGuestUserLoginButtonClick(String serverUrl, final boolean isSslDisabled) { + view.hideError(); if (TextUtils.isEmpty(serverUrl)) { - mView.showServerUrlCanNotBeEmptyError(); + view.showServerUrlCanNotBeEmptyError(); return; } - mView.showProgressDialog(); - mDataManager.authGuestUser(new CustomOnLoadingListener() { + view.showProgressDialog(); + dataManager.authGuestUser(new CustomOnLoadingListener() { @Override public void onSuccess(String serverUrl) { - mView.dismissProgressDialog(); - mDataManager.saveGuestUserAccount(serverUrl); - mDataManager.initTeamCityService(serverUrl); - mRouter.openProjectsRootPageForFirstStart(); - mTracker.trackGuestUserLoginSuccess(); - mView.close(); + view.dismissProgressDialog(); + dataManager.saveGuestUserAccount(serverUrl, isSslDisabled); + dataManager.initTeamCityService(serverUrl); + router.openProjectsRootPageForFirstStart(); + tracker.trackGuestUserLoginSuccess(!isSslDisabled); + view.close(); } @Override public void onFail(int statusCode, String errorMessage) { - mView.dismissProgressDialog(); - mView.showError(errorMessage); - mTracker.trackGuestUserLoginFailed(errorMessage); - mView.hideKeyboard(); + view.dismissProgressDialog(); + view.showError(errorMessage); + tracker.trackGuestUserLoginFailed(errorMessage); + view.hideKeyboard(); if (statusCode == UNAUTHORIZED_STATUS_CODE) { - mView.showUnauthorizedInfoDialog(); + view.showUnauthorizedInfoDialog(); } } - }, serverUrl); + }, serverUrl, isSslDisabled); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDisableSslSwitchClick() { + view.showDisableSslWarningDialog(); } } diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/login/tracker/LoginTrackerImpl.java b/app/src/main/java/com/github/vase4kin/teamcityapp/login/tracker/LoginTrackerImpl.java index 621ff0ef..c3eab167 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/login/tracker/LoginTrackerImpl.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/login/tracker/LoginTrackerImpl.java @@ -46,11 +46,11 @@ public void execute(LoginTracker tracker) { * {@inheritDoc} */ @Override - public void trackUserLoginSuccess() { + public void trackUserLoginSuccess(final boolean isSslEnabled) { logEvent(new TrackerMethod() { @Override public void execute(LoginTracker tracker) { - tracker.trackUserLoginSuccess(); + tracker.trackUserLoginSuccess(isSslEnabled); } }); } @@ -59,11 +59,11 @@ public void execute(LoginTracker tracker) { * {@inheritDoc} */ @Override - public void trackGuestUserLoginSuccess() { + public void trackGuestUserLoginSuccess(final boolean isSslEnabled) { logEvent(new TrackerMethod() { @Override public void execute(LoginTracker tracker) { - tracker.trackGuestUserLoginSuccess(); + tracker.trackGuestUserLoginSuccess(isSslEnabled); } }); } diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/LoginActivity.java b/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/LoginActivity.java index e8292a94..72d2a63b 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/LoginActivity.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/LoginActivity.java @@ -19,7 +19,6 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; @@ -48,10 +47,8 @@ public class LoginActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { setTheme(R.style.AppTheme); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); - } + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/LoginView.java b/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/LoginView.java index 84426baf..0abbfcef 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/LoginView.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/LoginView.java @@ -25,7 +25,7 @@ public interface LoginView { * Init views * @param listener */ - void initViews(OnLoginButtonClickListener listener); + void initViews(ViewListener listener); /** * Close activity @@ -98,4 +98,36 @@ public interface LoginView { * Show permissions unauthorized dialog */ void showUnauthorizedInfoDialog(); + + /** + * Show warning dialog about disabling ssl + */ + void showDisableSslWarningDialog(); + + /** + * Receiving callback from {@link LoginViewImpl} to {@link com.github.vase4kin.teamcityapp.login.presenter.LoginPresenterImpl} + */ + interface ViewListener { + + /** + * Handle on login button click for user creation + * + * @param serverUrl - TeamCity server url + * @param userName - User name + * @param password - User password + */ + void onUserLoginButtonClick(String serverUrl, String userName, String password, boolean isSslDisabled); + + /** + * Handle on login button click for guest user creation + * + * @param serverUrl - TeamCity server url + */ + void onGuestUserLoginButtonClick(String serverUrl, boolean isSslDisabled); + + /** + * On ignore ssl switch click + */ + void onDisableSslSwitchClick(); + } } diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/LoginViewImpl.java b/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/LoginViewImpl.java index 9de2a21e..48ad44c3 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/LoginViewImpl.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/LoginViewImpl.java @@ -18,6 +18,7 @@ import android.app.Activity; import android.content.Context; +import android.support.annotation.NonNull; import android.support.annotation.StringRes; import android.support.design.widget.TextInputLayout; import android.support.v4.view.ViewCompat; @@ -35,6 +36,7 @@ import android.widget.Switch; import android.widget.TextView; +import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.github.vase4kin.teamcityapp.R; @@ -72,6 +74,9 @@ public class LoginViewImpl implements LoginView { @BindView(R.id.img_logo) ImageView mLogoImageView; + @BindView(R.id.img_real_logo) + ImageView mRealLogoImageView; + @BindView(R.id.container) ViewGroup mContainer; @@ -96,6 +101,9 @@ public class LoginViewImpl implements LoginView { @BindView(R.id.guest_user_switch) Switch mGuestUserSwitch; + @BindView(R.id.disable_ssl_switch) + Switch disableSslSwitch; + @BindView(R.id.btn_login) Button mLoginButton; @@ -112,7 +120,7 @@ public LoginViewImpl(Activity mActivity) { * {@inheritDoc} */ @Override - public void initViews(final OnLoginButtonClickListener listener) { + public void initViews(final ViewListener listener) { mUnbinder = ButterKnife.bind(this, mActivity); mProgressDialog = new MaterialDialog.Builder(mActivity) @@ -133,7 +141,8 @@ public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent listener.onUserLoginButtonClick( mServerUrl.getText().toString().trim(), mUserName.getText().toString().trim(), - mPassword.getText().toString().trim()); + mPassword.getText().toString().trim(), + disableSslSwitch.isChecked()); return true; } return false; @@ -156,6 +165,15 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean b) { //Set text selection to the end mServerUrl.setSelection(mServerUrl.getText().length()); + + disableSslSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + listener.onDisableSslSwitchClick(); + } + } + }); } /** @@ -164,7 +182,8 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean b) { * @param isGuestUser - Is guest user enabled * @param listener - listener */ - private void setupViewsRegardingUserType(boolean isGuestUser, final OnLoginButtonClickListener listener) { + private void setupViewsRegardingUserType(boolean isGuestUser, + final ViewListener listener) { if (isGuestUser) { // guest user mServerUrl.setImeOptions(EditorInfo.IME_ACTION_DONE); @@ -172,7 +191,8 @@ private void setupViewsRegardingUserType(boolean isGuestUser, final OnLoginButto @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE) { - listener.onGuestUserLoginButtonClick(v.getText().toString().trim()); + listener.onGuestUserLoginButtonClick( + v.getText().toString().trim(), disableSslSwitch.isChecked()); return true; } return false; @@ -182,7 +202,8 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { @Override public void onClick(View v) { listener.onGuestUserLoginButtonClick( - mServerUrl.getText().toString().trim()); + mServerUrl.getText().toString().trim(), + disableSslSwitch.isChecked()); } }); } else { @@ -195,7 +216,8 @@ public void onClick(View v) { listener.onUserLoginButtonClick( mServerUrl.getText().toString().trim(), mUserName.getText().toString().trim(), - mPassword.getText().toString().trim()); + mPassword.getText().toString().trim(), + disableSslSwitch.isChecked()); } }); } @@ -234,7 +256,14 @@ public void animate() { .translationY(mActivity.getResources().getInteger(R.integer.logo_move)) .setStartDelay(STARTUP_DELAY) .setDuration(ANIM_ITEM_DURATION).setInterpolator( - new DecelerateInterpolator(1.2f)).start(); + new DecelerateInterpolator(1.2f)) + .withEndAction(new Runnable() { + @Override + public void run() { + mLogoImageView.setVisibility(View.GONE); + mRealLogoImageView.setVisibility(View.VISIBLE); + } + }).start(); for (int i = 0; i < mContainer.getChildCount(); i++) { View v = mContainer.getChildAt(i); @@ -342,6 +371,43 @@ public void showUnauthorizedInfoDialog() { .show(); } + /** + * {@inheritDoc} + */ + @Override + public void showDisableSslWarningDialog() { + new MaterialDialog.Builder(mActivity) + .titleColor(mWhiteColor) + .title(R.string.warning_ssl_dialog_title) + .content(R.string.warning_ssl_dialog_content) + .widgetColor(mWhiteColor) + .contentColor(mWhiteColor) + .backgroundColor(mPrimaryColor) + .positiveColor(mOrangeColor) + .positiveText(R.string.dialog_ok_title) + .negativeColor(mOrangeColor) + .negativeText(R.string.warning_ssl_dialog_negative) + .linkColor(mOrangeColor) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + disableSslSwitch.setChecked(true); + dialog.dismiss(); + } + }) + .onNegative(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + disableSslSwitch.setChecked(false); + dialog.dismiss(); + } + }) + .canceledOnTouchOutside(false) + .autoDismiss(false) + .cancelable(false) + .show(); + } + /** * Set error with string resource id * diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/OnLoginButtonClickListener.java b/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/OnLoginButtonClickListener.java deleted file mode 100644 index 6c9b05a9..00000000 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/login/view/OnLoginButtonClickListener.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2016 Andrey Tolpeev - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.github.vase4kin.teamcityapp.login.view; - -/** - * Receiving callback from {@link LoginViewImpl} to {@link com.github.vase4kin.teamcityapp.login.presenter.LoginPresenterImpl} - */ -public interface OnLoginButtonClickListener { - - /** - * Handle on login button click for user creation - * - * @param serverUrl - TeamCity server url - * @param userName - User name - * @param password - User password - */ - void onUserLoginButtonClick(String serverUrl, String userName, String password); - - /** - * Handle on login button click for guest user creation - * - * @param serverUrl - TeamCity server url - */ - void onGuestUserLoginButtonClick(String serverUrl); - -} diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/storage/SharedUserStorage.java b/app/src/main/java/com/github/vase4kin/teamcityapp/storage/SharedUserStorage.java index 534b20b1..aa689209 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/storage/SharedUserStorage.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/storage/SharedUserStorage.java @@ -107,8 +107,9 @@ public boolean hasUserAccounts() { return !getUserAccounts().isEmpty(); } - public void saveGuestUserAccountAndSetItAsActive(String baseUrl) { + public void saveGuestUserAccountAndSetItAsActive(String baseUrl, boolean disableSsl) { UserAccount userAccount = UsersFactory.guestUser(baseUrl); + userAccount.setSslDisabled(disableSsl); setActiveUserNotActive(); usersContainer.getUsersAccounts().add(userAccount); commitUserChanges(); @@ -117,10 +118,12 @@ public void saveGuestUserAccountAndSetItAsActive(String baseUrl) { public void saveUserAccountAndSetItAsActive(final String baseUrl, final String userName, final String password, + boolean disableSsl, OnStorageListener listener) { byte[] encryptedPassword = mCryptoManager.encrypt(password); if (!mCryptoManager.isFailed(encryptedPassword)) { UserAccount userAccount = UsersFactory.user(baseUrl, userName, encryptedPassword); + userAccount.setSslDisabled(disableSsl); setActiveUserNotActive(); usersContainer.getUsersAccounts().add(userAccount); commitUserChanges(); @@ -142,7 +145,9 @@ public UserAccount getActiveUser() { // backward compatibility for old user accounts // Who's gonna set 'Guest user' as real user name for TC user, right? if (userAccount.getUserName().equals(UsersFactory.GUEST_USER_USER_NAME)) { - return UsersFactory.guestUser(userAccount.getTeamcityUrl()); + UserAccount guestUser = UsersFactory.guestUser(userAccount.getTeamcityUrl()); + guestUser.setSslDisabled(userAccount.isSslDisabled()); + return guestUser; } // Don't need to decrypt password of guest user if (userAccount.isGuestUser()) { @@ -151,7 +156,9 @@ public UserAccount getActiveUser() { // Decrypting password for user byte[] decryptedPassword = mCryptoManager.decrypt(userAccount.getPasswordAsBytes()); if (!mCryptoManager.isFailed(decryptedPassword)) { - return UsersFactory.user(userAccount, decryptedPassword); + UserAccount user = UsersFactory.user(userAccount, decryptedPassword); + user.setSslDisabled(userAccount.isSslDisabled()); + return user; } else { return UsersFactory.EMPTY_USER; } diff --git a/app/src/main/java/com/github/vase4kin/teamcityapp/storage/api/UserAccount.java b/app/src/main/java/com/github/vase4kin/teamcityapp/storage/api/UserAccount.java index d89e3c59..a9b6acc4 100644 --- a/app/src/main/java/com/github/vase4kin/teamcityapp/storage/api/UserAccount.java +++ b/app/src/main/java/com/github/vase4kin/teamcityapp/storage/api/UserAccount.java @@ -28,6 +28,7 @@ public class UserAccount implements Jsonable { private byte[] password; private boolean isGuestUser; private boolean isActive; + private boolean isSslDisabled; public UserAccount(String teamcityUrl, String userName, @@ -39,6 +40,7 @@ public UserAccount(String teamcityUrl, this.password = password; this.isGuestUser = isGuestUser; this.isActive = isActive; + this.isSslDisabled = false; } public String getTeamcityUrl() { @@ -69,6 +71,14 @@ public void setIsActive(boolean isActive) { this.isActive = isActive; } + public void setSslDisabled(boolean sslDisabled) { + isSslDisabled = sslDisabled; + } + + public boolean isSslDisabled() { + return isSslDisabled; + } + @Override public String getId() { return teamcityUrl; diff --git a/app/src/main/res/layout/activity_create_account.xml b/app/src/main/res/layout/activity_create_account.xml index f9081c41..d8b7013a 100644 --- a/app/src/main/res/layout/activity_create_account.xml +++ b/app/src/main/res/layout/activity_create_account.xml @@ -110,6 +110,16 @@ + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index e502216d..e4e5e47b 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -23,7 +23,8 @@ + android:layout_height="match_parent" + android:fillViewport="true"> + android:paddingTop="48dp"> + + + +