From d796702ade5c5ad240cb6f8cefa99c3fcaf1cc98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Pich?= Date: Fri, 27 Oct 2017 17:48:17 +0200 Subject: [PATCH] New login UI (#29) * Refactor activity names * Refactor login activity and async task * Add forgot password and create account links * Login without passing symbol in login form * Add heading text before login form * Refactor API/login * Add login loading steps * Remove unnecessary try catch * Refactor snp and add tests * Remove redudant throws clauses --- app/build.gradle | 3 +- ...est.java => AccountAuthorizationTest.java} | 62 ++---- .../AccountRegistrationTest.java | 115 ++++++++++ app/src/main/AndroidManifest.xml | 8 +- .../activity/login/LoginActivity.java | 210 ++++++++++++++++++ .../wulkanowy/activity/login/LoginTask.java | 186 ++++++++++++++++ .../wulkanowy/activity/main/LoginTask.java | 90 -------- .../wulkanowy/activity/main/MainActivity.java | 111 --------- .../{started => splash}/LoadingTask.java | 7 +- .../SplashActivity.java} | 6 +- .../java/io/github/wulkanowy/api/Api.java | 38 ++-- .../java/io/github/wulkanowy/api/Cookies.java | 3 +- .../wulkanowy/api/StudentAndParent.java | 10 +- .../java/io/github/wulkanowy/api/Vulcan.java | 32 ++- .../wulkanowy/api/grades/GradesList.java | 2 +- .../wulkanowy/api/grades/SubjectsList.java | 2 +- .../io/github/wulkanowy/api/login/Login.java | 108 ++++----- .../wulkanowy/api/notes/AchievementsList.java | 2 +- .../github/wulkanowy/api/notes/NotesList.java | 2 +- .../wulkanowy/api/school/SchoolInfo.java | 2 +- .../wulkanowy/api/school/TeachersData.java | 3 + .../wulkanowy/api/school/TeachersInfo.java | 2 +- .../wulkanowy/api/timetable/Timetable.java | 2 +- .../wulkanowy/api/user/BasicInformation.java | 2 +- .../wulkanowy/api/user/FamilyInformation.java | 4 +- .../wulkanowy/dao/entities/Account.java | 19 +- .../github/wulkanowy/dao/entities/Grade.java | 4 +- .../services/VulcanSynchronization.java | 17 +- ...isation.java => AccountAuthorization.java} | 54 ++--- .../synchronisation/AccountRegistration.java | 82 +++++++ .../SubjectsSynchronisation.java | 2 +- app/src/main/res/drawable/ic_arrow_down.xml | 12 + app/src/main/res/layout/activity_login.xml | 155 +++++++++++++ app/src/main/res/layout/activity_main.xml | 65 ------ ...tivity_started.xml => activity_splash.xml} | 0 app/src/main/res/values-pl/strings.xml | 30 ++- app/src/main/res/values/dimens.xml | 4 + app/src/main/res/values/strings.xml | 30 ++- .../io/github/wulkanowy/api/VulcanTest.java | 36 ++- .../github/wulkanowy/api/login/LoginTest.java | 119 +++++++++- .../api/login/Logowanie-brak-dostepu.html | 15 ++ .../api/login/Logowanie-certyfikat.html | 17 ++ .../wulkanowy/api/login/Logowanie-error.html | 18 ++ .../api/login/Logowanie-success.html | 16 ++ .../io/github/wulkanowy/api/login/cert.xml | 18 ++ gradle/wrapper/gradle-wrapper.properties | 2 +- 46 files changed, 1221 insertions(+), 506 deletions(-) rename app/src/androidTest/java/io/github/wulkanowy/services/synchronization/{AccountSynchronizationTest.java => AccountAuthorizationTest.java} (59%) create mode 100644 app/src/androidTest/java/io/github/wulkanowy/services/synchronization/AccountRegistrationTest.java create mode 100644 app/src/main/java/io/github/wulkanowy/activity/login/LoginActivity.java create mode 100644 app/src/main/java/io/github/wulkanowy/activity/login/LoginTask.java delete mode 100644 app/src/main/java/io/github/wulkanowy/activity/main/LoginTask.java delete mode 100644 app/src/main/java/io/github/wulkanowy/activity/main/MainActivity.java rename app/src/main/java/io/github/wulkanowy/activity/{started => splash}/LoadingTask.java (88%) rename app/src/main/java/io/github/wulkanowy/activity/{started/StartedActivity.java => splash/SplashActivity.java} (76%) rename app/src/main/java/io/github/wulkanowy/services/synchronisation/{AccountSynchronisation.java => AccountAuthorization.java} (51%) create mode 100644 app/src/main/java/io/github/wulkanowy/services/synchronisation/AccountRegistration.java create mode 100644 app/src/main/res/drawable/ic_arrow_down.xml create mode 100644 app/src/main/res/layout/activity_login.xml delete mode 100644 app/src/main/res/layout/activity_main.xml rename app/src/main/res/layout/{activity_started.xml => activity_splash.xml} (100%) create mode 100644 app/src/test/resources/io/github/wulkanowy/api/login/Logowanie-brak-dostepu.html create mode 100644 app/src/test/resources/io/github/wulkanowy/api/login/Logowanie-certyfikat.html create mode 100644 app/src/test/resources/io/github/wulkanowy/api/login/Logowanie-error.html create mode 100644 app/src/test/resources/io/github/wulkanowy/api/login/Logowanie-success.html create mode 100644 app/src/test/resources/io/github/wulkanowy/api/login/cert.xml diff --git a/app/build.gradle b/app/build.gradle index 1513508b9..4d8a0965c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -39,7 +39,7 @@ android { } greendao { - schemaVersion 13 + schemaVersion 14 generateTests = true } @@ -57,6 +57,7 @@ dependencies { compile 'org.apache.commons:commons-collections4:4.1' compile 'org.jsoup:jsoup:1.10.3' compile 'org.greenrobot:greendao:3.2.2' + compile 'com.android.support:customtabs:27.0.0' debugCompile 'com.amitshekhar.android:debug-db:1.0.1' debugCompile 'net.zetetic:android-database-sqlcipher:3.5.7@aar' diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/synchronization/AccountSynchronizationTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/synchronization/AccountAuthorizationTest.java similarity index 59% rename from app/src/androidTest/java/io/github/wulkanowy/services/synchronization/AccountSynchronizationTest.java rename to app/src/androidTest/java/io/github/wulkanowy/services/synchronization/AccountAuthorizationTest.java index d070aee47..164868b63 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/services/synchronization/AccountSynchronizationTest.java +++ b/app/src/androidTest/java/io/github/wulkanowy/services/synchronization/AccountAuthorizationTest.java @@ -20,8 +20,6 @@ import io.github.wulkanowy.api.Vulcan; import io.github.wulkanowy.api.login.AccountPermissionException; import io.github.wulkanowy.api.login.BadCredentialsException; import io.github.wulkanowy.api.login.LoginErrorException; -import io.github.wulkanowy.api.user.BasicInformation; -import io.github.wulkanowy.api.user.PersonalData; import io.github.wulkanowy.dao.entities.Account; import io.github.wulkanowy.dao.entities.AccountDao; import io.github.wulkanowy.dao.entities.DaoMaster; @@ -29,10 +27,10 @@ import io.github.wulkanowy.dao.entities.DaoSession; import io.github.wulkanowy.security.CryptoException; import io.github.wulkanowy.security.Safety; import io.github.wulkanowy.services.LoginSession; -import io.github.wulkanowy.services.synchronisation.AccountSynchronisation; +import io.github.wulkanowy.services.synchronisation.AccountAuthorization; @RunWith(AndroidJUnit4.class) -public class AccountSynchronizationTest { +public class AccountAuthorizationTest { private static DaoSession daoSession; @@ -64,8 +62,8 @@ public class AccountSynchronizationTest { public void emptyUserIdTest() throws CryptoException, BadCredentialsException, AccountPermissionException, IOException, LoginErrorException { - AccountSynchronisation accountSynchronisation = new AccountSynchronisation(); - accountSynchronisation.loginCurrentUser(context, daoSession, new Vulcan()); + AccountAuthorization accountAuthorization = new AccountAuthorization(context, daoSession, new Vulcan()); + accountAuthorization.loginCurrentUser(); } @Test @@ -82,10 +80,10 @@ public class AccountSynchronizationTest { setUserIdSharePreferences(userId); Vulcan vulcan = Mockito.mock(Vulcan.class); - Mockito.doNothing().when(vulcan).login("TEST@TEST", "TEST", ""); + Mockito.doNothing().when(vulcan).login("TEST@TEST", "TEST", "TEST_SYMBOL", "TEST_ID"); - AccountSynchronisation accountSynchronisation = new AccountSynchronisation(); - LoginSession loginSession = accountSynchronisation.loginCurrentUser(targetContext, daoSession, vulcan); + AccountAuthorization accountAuthorization = new AccountAuthorization(targetContext, daoSession, vulcan); + LoginSession loginSession = accountAuthorization.loginCurrentUser(); Assert.assertNotNull(loginSession); Assert.assertEquals(loginSession.getUserId(), userId); @@ -93,50 +91,16 @@ public class AccountSynchronizationTest { Assert.assertEquals(loginSession.getVulcan(), vulcan); } - @Test - public void loginNewUserTest() throws Exception { - PersonalData personalData = Mockito.mock(PersonalData.class); - Mockito.doReturn("NAME-TEST").when(personalData).getFirstAndLastName(); - - BasicInformation basicInformation = Mockito.mock(BasicInformation.class); - Mockito.doReturn(personalData).when(basicInformation).getPersonalData(); - - Vulcan vulcan = Mockito.mock(Vulcan.class); - Mockito.doNothing().when(vulcan).login("TEST@TEST", "TEST", ""); - Mockito.doReturn(basicInformation).when(vulcan).getBasicInformation(); - - AccountSynchronisation accountSynchronisation = new AccountSynchronisation(); - LoginSession loginSession = accountSynchronisation - .loginNewUser("TEST@TEST", "TEST", "", targetContext, daoSession, vulcan); - - Long userId = targetContext.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("userId", 0); - - Assert.assertNotNull(loginSession); - Assert.assertNotEquals(0, userId.longValue()); - Assert.assertEquals(loginSession.getUserId(), userId); - Assert.assertNotNull(loginSession.getDaoSession()); - Assert.assertEquals(loginSession.getVulcan(), vulcan); - - Safety safety = new Safety(); - Account account = daoSession.getAccountDao().load(userId); - - Assert.assertNotNull(account); - Assert.assertEquals("TEST@TEST", account.getEmail()); - Assert.assertEquals("NAME-TEST", account.getName()); - Assert.assertEquals("TEST", safety.decrypt("TEST@TEST", account.getPassword())); - - } - - @AfterClass - public static void cleanUp() { - daoSession.getAccountDao().deleteAll(); - daoSession.clear(); - } - private void setUserIdSharePreferences(long id) { SharedPreferences sharedPreferences = targetContext.getSharedPreferences("LoginData", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putLong("userId", id); editor.apply(); } + + @AfterClass + public static void cleanUp() { + daoSession.getAccountDao().deleteAll(); + daoSession.clear(); + } } diff --git a/app/src/androidTest/java/io/github/wulkanowy/services/synchronization/AccountRegistrationTest.java b/app/src/androidTest/java/io/github/wulkanowy/services/synchronization/AccountRegistrationTest.java new file mode 100644 index 000000000..0a68e62fa --- /dev/null +++ b/app/src/androidTest/java/io/github/wulkanowy/services/synchronization/AccountRegistrationTest.java @@ -0,0 +1,115 @@ +package io.github.wulkanowy.services.synchronization; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.test.InstrumentationRegistry; + +import org.greenrobot.greendao.database.Database; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mockito; + +import io.github.wulkanowy.api.StudentAndParent; +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.login.Login; +import io.github.wulkanowy.api.user.BasicInformation; +import io.github.wulkanowy.api.user.PersonalData; +import io.github.wulkanowy.dao.entities.Account; +import io.github.wulkanowy.dao.entities.DaoMaster; +import io.github.wulkanowy.dao.entities.DaoSession; +import io.github.wulkanowy.security.Safety; +import io.github.wulkanowy.services.LoginSession; +import io.github.wulkanowy.services.synchronisation.AccountRegistration; + +public class AccountRegistrationTest { + + private static DaoSession daoSession; + + private Context targetContext; + + @BeforeClass + public static void setUpClass() { + + DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(InstrumentationRegistry.getTargetContext(), "wulkanowyTest-database"); + Database database = devOpenHelper.getWritableDb(); + + daoSession = new DaoMaster(database).newSession(); + } + + @Before + public void setUp() { + targetContext = InstrumentationRegistry.getTargetContext(); + + daoSession.getAccountDao().deleteAll(); + daoSession.clear(); + + setUserIdSharePreferences(0); + } + + @Test + public void connectTest() throws Exception { + String certificate = "Certificate"; + Login login = Mockito.mock(Login.class); + Mockito.when(login.sendCredentials(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())) + .thenReturn(certificate); + AccountRegistration accountRegistration = new AccountRegistration(login, new Vulcan(), "TEST@TEST", "TEST_PASS", "TEST_SYMBOL"); + + Assert.assertEquals(certificate, accountRegistration.connect()); + } + + @Test + public void loginTest() throws Exception { + StudentAndParent snp = Mockito.mock(StudentAndParent.class); + Mockito.when(snp.getSymbol()).thenReturn("TEST-SYMBOL"); + Mockito.when(snp.getId()).thenReturn("TEST-ID"); + + PersonalData personalData = Mockito.mock(PersonalData.class); + Mockito.doReturn("NAME-TEST").when(personalData).getFirstAndLastName(); + + BasicInformation basicInformation = Mockito.mock(BasicInformation.class); + Mockito.doReturn(personalData).when(basicInformation).getPersonalData(); + + Vulcan vulcan = Mockito.mock(Vulcan.class); + Mockito.doReturn(basicInformation).when(vulcan).getBasicInformation(); + Mockito.doReturn(snp).when(vulcan).getStudentAndParent(); + + Login login = Mockito.mock(Login.class); + Mockito.when(login.sendCertificate(Mockito.anyString(), Mockito.anyString())).thenReturn("TEST-SYMBOL"); + + AccountRegistration accountRegistration = new AccountRegistration(login, vulcan, "TEST@TEST", "TEST-PASS", "default"); + LoginSession loginSession = accountRegistration.login(targetContext, daoSession, "cert"); + + Long userId = targetContext.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("userId", 0); + + Assert.assertNotNull(loginSession); + Assert.assertNotEquals(0, userId.longValue()); + Assert.assertEquals(loginSession.getUserId(), userId); + Assert.assertNotNull(loginSession.getDaoSession()); + Assert.assertEquals(loginSession.getVulcan(), vulcan); + + Safety safety = new Safety(); + Account account = daoSession.getAccountDao().load(userId); + Assert.assertNotNull(account); + Assert.assertEquals("TEST@TEST", account.getEmail()); + Assert.assertEquals("NAME-TEST", account.getName()); + Assert.assertEquals("TEST-PASS", safety.decrypt("TEST@TEST", account.getPassword())); + Assert.assertEquals("TEST-SYMBOL", account.getSymbol()); + Assert.assertEquals("TEST-ID", account.getSnpId()); + } + + private void setUserIdSharePreferences(long id) { + SharedPreferences sharedPreferences = targetContext.getSharedPreferences("LoginData", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putLong("userId", id); + editor.apply(); + } + + @AfterClass + public static void cleanUp() { + daoSession.getAccountDao().deleteAll(); + daoSession.clear(); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 15c4d7893..95b204cf5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,7 +19,7 @@ android:supportsRtl="true" android:theme="@style/WulkanowyTheme"> @@ -30,9 +30,9 @@ - \ No newline at end of file + diff --git a/app/src/main/java/io/github/wulkanowy/activity/login/LoginActivity.java b/app/src/main/java/io/github/wulkanowy/activity/login/LoginActivity.java new file mode 100644 index 000000000..b690f8c9a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/activity/login/LoginActivity.java @@ -0,0 +1,210 @@ +package io.github.wulkanowy.activity.login; + +import android.app.Activity; +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; +import android.support.customtabs.CustomTabsIntent; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import java.util.LinkedHashMap; + +import io.github.wulkanowy.R; + +/** + * A login screen that offers login via email/password. + */ +public class LoginActivity extends Activity { + + private float touchPosition; + + private EditText emailView; + + private EditText passwordView; + + private AutoCompleteTextView symbolView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + + // Set up the login form. + emailView = findViewById(R.id.email); + passwordView = findViewById(R.id.password); + symbolView = findViewById(R.id.symbol); + + passwordView.setOnEditorActionListener(getTextViewSignInListener()); + symbolView.setOnEditorActionListener(getTextViewSignInListener()); + + populateAutoComplete(); + + Button signInButton = findViewById(R.id.action_sign_in); + signInButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + attemptLogin(); + } + }); + + findViewById(R.id.action_create_account).setOnClickListener(getButtonLinkListener( + "https://cufs.vulcan.net.pl/Default/AccountManage/CreateAccount" + )); + + findViewById(R.id.action_forgot_password).setOnClickListener(getButtonLinkListener( + "https://cufs.vulcan.net.pl/Default/AccountManage/UnlockAccount" + )); + } + + private TextView.OnEditorActionListener getTextViewSignInListener() { + return new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { + if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) { + attemptLogin(); + return true; + } + return false; + } + }; + } + + private OnClickListener getButtonLinkListener(final String url) { + return new OnClickListener() { + @Override + public void onClick(View view) { + CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); + CustomTabsIntent customTabsIntent = builder.build(); + builder.setToolbarColor(getResources().getColor(R.color.colorPrimary)); + customTabsIntent.launchUrl(view.getContext(), Uri.parse(url)); + } + }; + } + + private void populateAutoComplete() { + // Get the string array + String[] countries = getResources().getStringArray(R.array.symbols); + // Create the adapter and set it to the AutoCompleteTextView + ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, + countries); + symbolView.setAdapter(adapter); + } + + /** + * Attempts to sign in the account specified by the login form. + */ + private void attemptLogin() { + // Reset errors. + emailView.setError(null); + passwordView.setError(null); + + // Store values at the time of the login attempt. + String email = emailView.getText().toString(); + String password = passwordView.getText().toString(); + String symbol = symbolView.getText().toString(); + + boolean cancel = false; + View focusView = null; + + // Check for a valid password. + if (TextUtils.isEmpty(password)) { + passwordView.setError(getString(R.string.error_field_required)); + focusView = passwordView; + cancel = true; + } else if (!isPasswordValid(password)) { + passwordView.setError(getString(R.string.error_invalid_password)); + focusView = passwordView; + cancel = true; + } + + // Check for a valid email address. + if (TextUtils.isEmpty(email)) { + emailView.setError(getString(R.string.error_field_required)); + focusView = emailView; + cancel = true; + } else if (!isEmailValid(email)) { + emailView.setError(getString(R.string.error_invalid_email)); + focusView = emailView; + cancel = true; + } + + // Check for a valid symbol. + if (TextUtils.isEmpty(symbol)) { + symbol = "Default"; + } + + String[] keys = this.getResources().getStringArray(R.array.symbols); + String[] values = this.getResources().getStringArray(R.array.symbols_values); + LinkedHashMap map = new LinkedHashMap<>(); + + for (int i = 0; i < Math.min(keys.length, values.length); ++i) { + map.put(keys[i], values[i]); + } + + if (map.containsKey(symbol)) { + symbol = map.get(symbol); + } + + if (cancel) { + // There was an error; don't attempt login and focus the first + // form field with an error. + focusView.requestFocus(); + } else { + // Show a progress spinner, and kick off a background task to + // perform the user login attempt. + LoginTask authTask = new LoginTask(this, email, password, symbol); + authTask.showProgress(true); + authTask.execute(); + } + } + + private boolean isEmailValid(String email) { + return email.contains("@"); + } + + private boolean isPasswordValid(String password) { + return password.length() > 7; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + touchPosition = ev.getY(); + } + if (ev.getAction() == MotionEvent.ACTION_UP) { + float releasePosition = ev.getY(); + + if (touchPosition - releasePosition == 0) { + View view = getCurrentFocus(); + if (view != null && (ev.getAction() == MotionEvent.ACTION_UP + || ev.getAction() == MotionEvent.ACTION_MOVE) && view instanceof EditText + && !view.getClass().getName().startsWith("android.webkit.")) { + + int scrcoords[] = new int[2]; + view.getLocationOnScreen(scrcoords); + float x = ev.getRawX() + view.getLeft() - scrcoords[0]; + float y = ev.getRawY() + view.getTop() - scrcoords[1]; + if (x < view.getLeft() || x > view.getRight() || y < view.getTop() + || y > view.getBottom()) { + ((InputMethodManager) this.getSystemService( + Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow( + (this.getWindow().getDecorView().getApplicationWindowToken()), 0); + } + } + } + } + return super.dispatchTouchEvent(ev); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/activity/login/LoginTask.java b/app/src/main/java/io/github/wulkanowy/activity/login/LoginTask.java new file mode 100644 index 000000000..57eeda425 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/activity/login/LoginTask.java @@ -0,0 +1,186 @@ +package io.github.wulkanowy.activity.login; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.app.Activity; +import android.content.Intent; +import android.os.AsyncTask; +import android.support.design.widget.Snackbar; +import android.support.design.widget.TextInputLayout; +import android.view.View; +import android.widget.EditText; +import android.widget.TextView; + +import java.io.IOException; + +import io.github.wulkanowy.R; +import io.github.wulkanowy.activity.WulkanowyApp; +import io.github.wulkanowy.activity.dashboard.DashboardActivity; +import io.github.wulkanowy.api.Cookies; +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.login.AccountPermissionException; +import io.github.wulkanowy.api.login.BadCredentialsException; +import io.github.wulkanowy.api.login.Login; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.dao.entities.DaoSession; +import io.github.wulkanowy.security.CryptoException; +import io.github.wulkanowy.services.LoginSession; +import io.github.wulkanowy.services.VulcanSynchronization; +import io.github.wulkanowy.services.synchronisation.AccountRegistration; +import io.github.wulkanowy.utilities.ConnectionUtilities; + +/** + * Represents an asynchronous login/registration task used to authenticate + * the user. + */ +public class LoginTask extends AsyncTask { + + private final String email; + + private final String password; + + private final String symbol; + + private Activity activity; + + private View progressView; + + private View loginFormView; + + private TextView showText; + + public LoginTask(Activity activity, String email, String password, String symbol) { + this.activity = activity; + this.email = email; + this.password = password; + this.symbol = symbol; + } + + @Override + protected void onPreExecute() { + showText = activity.findViewById(R.id.login_progress_text); + } + + @Override + protected Integer doInBackground(Void... params) { + if (ConnectionUtilities.isOnline(activity)) { + AccountRegistration accountRegistration = new AccountRegistration( + new Login(new Cookies()), + new Vulcan(), + email, password, symbol); + + DaoSession daoSession = ((WulkanowyApp) activity.getApplication()).getDaoSession(); + + try { + publishProgress("1", activity.getResources().getString(R.string.step_connecting)); + String certificate = accountRegistration.connect(); + + publishProgress("2", activity.getResources().getString(R.string.step_login)); + LoginSession loginSession = accountRegistration.login(activity, daoSession, certificate); + + publishProgress("3", activity.getResources().getString(R.string.step_synchronization)); + VulcanSynchronization vulcanSynchronization = new VulcanSynchronization(loginSession); + vulcanSynchronization.syncSubjectsAndGrades(); + + } catch (BadCredentialsException e) { + return R.string.login_bad_credentials_text; + } catch (AccountPermissionException e) { + return R.string.error_bad_account_permission; + } catch (CryptoException e) { + return R.string.encrypt_failed_text; + } catch (NotLoggedInErrorException | IOException e) { + return R.string.login_denied_text; + } + + accountRegistration.scheduleSynchronization(activity); + + return R.string.login_accepted_text; + + } else { + return R.string.noInternet_text; + } + } + + @Override + protected void onProgressUpdate(String... progress) { + showText.setText(progress[0] + "/3 - " + progress[1] + "..."); + } + + @Override + protected void onPostExecute(final Integer messageID) { + showProgress(false); + + switch (messageID) { + // if success + case R.string.login_accepted_text: + Intent intent = new Intent(activity, DashboardActivity.class); + activity.finish(); + activity.startActivity(intent); + break; + + // if bad credentials entered + case R.string.login_bad_credentials_text: + EditText passwordView = activity.findViewById(R.id.password); + passwordView.setError(activity.getString(R.string.error_incorrect_password)); + passwordView.requestFocus(); + break; + + // if no permission + case R.string.error_bad_account_permission: + // Change to visible symbol input view + TextInputLayout symbolLayout = activity.findViewById(R.id.to_symbol_input_layout); + symbolLayout.setVisibility(View.VISIBLE); + + EditText symbolView = activity.findViewById(R.id.symbol); + symbolView.setError(activity.getString(R.string.error_bad_account_permission)); + symbolView.requestFocus(); + break; + + default: + Snackbar + .make(activity.findViewById(R.id.coordinatorLayout), messageID, Snackbar.LENGTH_LONG) + .show(); + break; + } + } + + @Override + protected void onCancelled() { + showProgress(false); + } + + /** + * Shows the progress UI and hides the login form. + */ + public void showProgress(final boolean show) { + loginFormView = activity.findViewById(R.id.login_form); + progressView = activity.findViewById(R.id.login_progress); + + int animTime = activity.getResources().getInteger(android.R.integer.config_shortAnimTime); + + changeLoginFormVisibility(show, animTime); + changeProgressVisibility(show, animTime); + } + + private void changeLoginFormVisibility(final boolean show, final int animTime) { + loginFormView.setVisibility(show ? View.GONE : View.VISIBLE); + loginFormView.animate().setDuration(animTime).alpha( + show ? 0 : 1).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + loginFormView.setVisibility(show ? View.GONE : View.VISIBLE); + } + }); + } + + private void changeProgressVisibility(final boolean show, final int animTime) { + progressView.setVisibility(show ? View.VISIBLE : View.GONE); + progressView.animate().setDuration(animTime).alpha( + show ? 1 : 0).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + progressView.setVisibility(show ? View.VISIBLE : View.GONE); + } + }); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/activity/main/LoginTask.java b/app/src/main/java/io/github/wulkanowy/activity/main/LoginTask.java deleted file mode 100644 index c1cc0458a..000000000 --- a/app/src/main/java/io/github/wulkanowy/activity/main/LoginTask.java +++ /dev/null @@ -1,90 +0,0 @@ -package io.github.wulkanowy.activity.main; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.Intent; -import android.os.AsyncTask; -import android.widget.Toast; - -import java.io.IOException; - -import io.github.wulkanowy.R; -import io.github.wulkanowy.activity.WulkanowyApp; -import io.github.wulkanowy.activity.dashboard.DashboardActivity; -import io.github.wulkanowy.api.Vulcan; -import io.github.wulkanowy.api.login.AccountPermissionException; -import io.github.wulkanowy.api.login.BadCredentialsException; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.dao.entities.DaoSession; -import io.github.wulkanowy.security.CryptoException; -import io.github.wulkanowy.services.LoginSession; -import io.github.wulkanowy.services.VulcanSynchronization; -import io.github.wulkanowy.services.jobs.GradeJob; -import io.github.wulkanowy.utilities.ConnectionUtilities; - -public class LoginTask extends AsyncTask { - - private Activity activity; - - private ProgressDialog progress; - - public LoginTask(Activity activity) { - this.activity = activity; - this.progress = new ProgressDialog(activity); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - - progress.setTitle(activity.getText(R.string.login_text)); - progress.setMessage(activity.getText(R.string.please_wait_text)); - progress.setCancelable(false); - progress.show(); - } - - @Override - protected Integer doInBackground(String... credentials) { - - if (ConnectionUtilities.isOnline(activity)) { - VulcanSynchronization vulcanSynchronization = new VulcanSynchronization(new LoginSession()); - DaoSession daoSession = ((WulkanowyApp) activity.getApplication()).getDaoSession(); - try { - vulcanSynchronization - .loginNewUser(credentials[0], credentials[1], credentials[2], activity, daoSession, new Vulcan()); - } catch (BadCredentialsException e) { - return R.string.login_bad_credentials_text; - } catch (AccountPermissionException e) { - return R.string.login_bad_account_permission_text; - } catch (CryptoException e) { - return R.string.encrypt_failed_text; - } catch (NotLoggedInErrorException | IOException e) { - return R.string.login_denied_text; - } - - vulcanSynchronization.syncSubjectsAndGrades(); - - return R.string.login_accepted_text; - - } else { - return R.string.noInternet_text; - } - } - - protected void onPostExecute(Integer messageID) { - super.onPostExecute(messageID); - - GradeJob gradesSync = new GradeJob(); - gradesSync.scheduledJob(activity); - - progress.dismiss(); - - Toast.makeText(activity, activity.getString(messageID), Toast.LENGTH_LONG).show(); - - if (messageID == R.string.login_accepted_text || messageID == R.string.root_failed_text - || messageID == R.string.encrypt_failed_text) { - Intent intent = new Intent(activity, DashboardActivity.class); - activity.startActivity(intent); - } - } -} diff --git a/app/src/main/java/io/github/wulkanowy/activity/main/MainActivity.java b/app/src/main/java/io/github/wulkanowy/activity/main/MainActivity.java deleted file mode 100644 index 1d162f4f3..000000000 --- a/app/src/main/java/io/github/wulkanowy/activity/main/MainActivity.java +++ /dev/null @@ -1,111 +0,0 @@ -package io.github.wulkanowy.activity.main; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.view.MotionEvent; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.widget.ArrayAdapter; -import android.widget.AutoCompleteTextView; -import android.widget.EditText; -import android.widget.Toast; - -import java.util.LinkedHashMap; - -import io.github.wulkanowy.R; - -public class MainActivity extends AppCompatActivity { - - private float mTouchPosition; - - private float mReleasePosition; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - new AlertDialog.Builder(this) - .setTitle(R.string.warning_label_text) - .setMessage(R.string.warning_text) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .setIcon(android.R.drawable.ic_dialog_alert) - .show(); - - autoComplete(); - } - - private void autoComplete() { - - // Get a reference to the AutoCompleteTextView in the layout - AutoCompleteTextView textView = findViewById(R.id.symbolText); - // Get the string array - String[] countries = getResources().getStringArray(R.array.symbols); - // Create the adapter and set it to the AutoCompleteTextView - ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, - countries); - textView.setAdapter(adapter); - } - - public void login(View a) { - String password = ((EditText) findViewById(R.id.passwordText)).getText().toString(); - String email = ((EditText) findViewById(R.id.emailText)).getText().toString(); - String symbol = ((EditText) findViewById(R.id.symbolText)).getText().toString(); - - String[] keys = this.getResources().getStringArray(R.array.symbols); - String[] values = this.getResources().getStringArray(R.array.symbols_values); - LinkedHashMap map = new LinkedHashMap<>(); - - for (int i = 0; i < Math.min(keys.length, values.length); ++i) { - map.put(keys[i], values[i]); - } - - if (map.containsKey(symbol)) { - symbol = map.get(symbol); - } - - if (!email.isEmpty() && !password.isEmpty() && !symbol.isEmpty()) { - new LoginTask(this).execute(email, password, symbol); - } else { - Toast.makeText(this, R.string.data_text, Toast.LENGTH_SHORT).show(); - } - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mTouchPosition = ev.getY(); - } - if (ev.getAction() == MotionEvent.ACTION_UP) { - mReleasePosition = ev.getY(); - - if (mTouchPosition - mReleasePosition == 0) { - View view = getCurrentFocus(); - if (view != null && (ev.getAction() == MotionEvent.ACTION_UP - || ev.getAction() == MotionEvent.ACTION_MOVE) && view instanceof EditText - && !view.getClass().getName().startsWith("android.webkit.")) { - - int scrcoords[] = new int[2]; - view.getLocationOnScreen(scrcoords); - float x = ev.getRawX() + view.getLeft() - scrcoords[0]; - float y = ev.getRawY() + view.getTop() - scrcoords[1]; - if (x < view.getLeft() || x > view.getRight() || y < view.getTop() - || y > view.getBottom()) { - ((InputMethodManager) this.getSystemService( - Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow( - (this.getWindow().getDecorView().getApplicationWindowToken()), 0); - } - } - } - } - return super.dispatchTouchEvent(ev); - } -} diff --git a/app/src/main/java/io/github/wulkanowy/activity/started/LoadingTask.java b/app/src/main/java/io/github/wulkanowy/activity/splash/LoadingTask.java similarity index 88% rename from app/src/main/java/io/github/wulkanowy/activity/started/LoadingTask.java rename to app/src/main/java/io/github/wulkanowy/activity/splash/LoadingTask.java index bdf0ee96f..cc89082b3 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/started/LoadingTask.java +++ b/app/src/main/java/io/github/wulkanowy/activity/splash/LoadingTask.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.activity.started; +package io.github.wulkanowy.activity.splash; import android.content.Context; import android.content.Intent; @@ -7,7 +7,7 @@ import android.widget.Toast; import io.github.wulkanowy.R; import io.github.wulkanowy.activity.dashboard.DashboardActivity; -import io.github.wulkanowy.activity.main.MainActivity; +import io.github.wulkanowy.activity.login.LoginActivity; import io.github.wulkanowy.services.jobs.GradeJob; import io.github.wulkanowy.utilities.ConnectionUtilities; @@ -39,7 +39,7 @@ public class LoadingTask extends AsyncTask { } if (context.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("userId", 0) == 0) { - Intent intent = new Intent(context, MainActivity.class); + Intent intent = new Intent(context, LoginActivity.class); context.startActivity(intent); } else { GradeJob gradesSync = new GradeJob(); @@ -49,6 +49,5 @@ public class LoadingTask extends AsyncTask { context.startActivity(intent); } - } } diff --git a/app/src/main/java/io/github/wulkanowy/activity/started/StartedActivity.java b/app/src/main/java/io/github/wulkanowy/activity/splash/SplashActivity.java similarity index 76% rename from app/src/main/java/io/github/wulkanowy/activity/started/StartedActivity.java rename to app/src/main/java/io/github/wulkanowy/activity/splash/SplashActivity.java index 3e3c90014..9b6a1a337 100644 --- a/app/src/main/java/io/github/wulkanowy/activity/started/StartedActivity.java +++ b/app/src/main/java/io/github/wulkanowy/activity/splash/SplashActivity.java @@ -1,4 +1,4 @@ -package io.github.wulkanowy.activity.started; +package io.github.wulkanowy.activity.splash; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; @@ -7,12 +7,12 @@ import android.widget.TextView; import io.github.wulkanowy.BuildConfig; import io.github.wulkanowy.R; -public class StartedActivity extends AppCompatActivity { +public class SplashActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_started); + setContentView(R.layout.activity_splash); TextView versionName = findViewById(R.id.rawText); versionName.setText(getText(R.string.version_text) + BuildConfig.VERSION_NAME); diff --git a/app/src/main/java/io/github/wulkanowy/api/Api.java b/app/src/main/java/io/github/wulkanowy/api/Api.java index e62fbdefa..fc9c03ee2 100644 --- a/app/src/main/java/io/github/wulkanowy/api/Api.java +++ b/app/src/main/java/io/github/wulkanowy/api/Api.java @@ -1,5 +1,6 @@ package io.github.wulkanowy.api; +import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -8,7 +9,7 @@ import java.util.Map; public abstract class Api { - protected Cookies cookies; + protected Cookies cookies = new Cookies(); public Cookies getCookiesObject() { return cookies; @@ -18,20 +19,31 @@ public abstract class Api { return cookies.getItems(); } - public Cookies setCookies(Map cookies) { - this.cookies.setItems(cookies); - return this.cookies; - } - - public Cookies addCookies(Map cookies) { - this.cookies.addItems(cookies); - return this.cookies; - } - public Document getPageByUrl(String url) throws IOException { - return Jsoup.connect(url) + Connection.Response response = Jsoup.connect(url) .followRedirects(true) .cookies(getCookies()) - .get(); + .execute(); + + this.cookies.addItems(response.cookies()); + + return response.parse(); + } + + public Document postPageByUrl(String url, String[][] params) throws IOException { + Connection connection = Jsoup.connect(url); + + for (String[] data : params) { + connection.data(data[0], data[1]); + } + + Connection.Response response = connection.cookies(getCookies()) + .followRedirects(true) + .method(Connection.Method.POST) + .execute(); + + this.cookies.addItems(response.cookies()); + + return response.parse(); } } diff --git a/app/src/main/java/io/github/wulkanowy/api/Cookies.java b/app/src/main/java/io/github/wulkanowy/api/Cookies.java index 6801c61a1..2fe49f312 100644 --- a/app/src/main/java/io/github/wulkanowy/api/Cookies.java +++ b/app/src/main/java/io/github/wulkanowy/api/Cookies.java @@ -1,10 +1,11 @@ package io.github.wulkanowy.api; +import java.util.HashMap; import java.util.Map; public class Cookies { - private Map cookies; + private Map cookies = new HashMap<>(); public Map getItems() { return cookies; diff --git a/app/src/main/java/io/github/wulkanowy/api/StudentAndParent.java b/app/src/main/java/io/github/wulkanowy/api/StudentAndParent.java index fb521df82..09e18fcbd 100644 --- a/app/src/main/java/io/github/wulkanowy/api/StudentAndParent.java +++ b/app/src/main/java/io/github/wulkanowy/api/StudentAndParent.java @@ -1,7 +1,5 @@ package io.github.wulkanowy.api; -import org.jsoup.Connection; -import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; @@ -55,13 +53,7 @@ public class StudentAndParent extends Api { } public void storeContextCookies() throws IOException, NotLoggedInErrorException { - //get context cookie - Connection.Response res = Jsoup.connect(getSnpPageUrl()) - .followRedirects(true) - .cookies(getCookies()) - .execute(); - - cookies.addItems(res.cookies()); + getPageByUrl(getSnpPageUrl()); } public String getSnpPageUrl() throws IOException, NotLoggedInErrorException { diff --git a/app/src/main/java/io/github/wulkanowy/api/Vulcan.java b/app/src/main/java/io/github/wulkanowy/api/Vulcan.java index 2b6d366ac..29200a5ed 100644 --- a/app/src/main/java/io/github/wulkanowy/api/Vulcan.java +++ b/app/src/main/java/io/github/wulkanowy/api/Vulcan.java @@ -27,17 +27,21 @@ public class Vulcan extends Api { private StudentAndParent snp; - public void login(String email, String password, String symbol) - throws BadCredentialsException, AccountPermissionException, LoginErrorException { - Login login = new Login(new Cookies()); - login.login(email, password, symbol); - + public void login(Cookies cookies, String symbol) { + this.cookies = cookies; this.symbol = symbol; - this.cookies = login.getCookiesObject(); + } + + public void login(String email, String password, String symbol) + throws BadCredentialsException, AccountPermissionException, LoginErrorException, IOException { + Login login = new Login(new Cookies()); + String realSymbol = login.login(email, password, symbol); + + login(login.getCookiesObject(), realSymbol); } public void login(String email, String password, String symbol, String id) - throws BadCredentialsException, AccountPermissionException, LoginErrorException { + throws BadCredentialsException, AccountPermissionException, LoginErrorException, IOException { login(email, password, symbol); this.id = id; @@ -52,11 +56,7 @@ public class Vulcan extends Api { return snp; } - if (null == id) { - snp = new StudentAndParent(cookies, symbol); - } else { - snp = new StudentAndParent(cookies, symbol, id); - } + snp = createSnp(cookies, symbol, id); snp.storeContextCookies(); @@ -65,6 +65,14 @@ public class Vulcan extends Api { return snp; } + public StudentAndParent createSnp(Cookies cookies, String symbol, String id) { + if (null == id) { + return new StudentAndParent(cookies, symbol); + } + + return new StudentAndParent(cookies, symbol, id); + } + public AttendanceStatistics getAttendanceStatistics() throws IOException, NotLoggedInErrorException { return new AttendanceStatistics(getStudentAndParent()); } diff --git a/app/src/main/java/io/github/wulkanowy/api/grades/GradesList.java b/app/src/main/java/io/github/wulkanowy/api/grades/GradesList.java index 1e665a27d..0c5a577a8 100644 --- a/app/src/main/java/io/github/wulkanowy/api/grades/GradesList.java +++ b/app/src/main/java/io/github/wulkanowy/api/grades/GradesList.java @@ -38,7 +38,7 @@ public class GradesList { return getAll(""); } - public List getAll(String semester) throws IOException, LoginErrorException, ParseException { + public List getAll(String semester) throws IOException, ParseException { Document gradesPage = snp.getSnPPageDocument(getGradesPageUrl() + semester); Elements gradesRows = gradesPage.select(".ocenySzczegoly-table > tbody > tr"); Semester currentSemester = snp.getCurrentSemester(snp.getSemesters(gradesPage)); diff --git a/app/src/main/java/io/github/wulkanowy/api/grades/SubjectsList.java b/app/src/main/java/io/github/wulkanowy/api/grades/SubjectsList.java index 24217242e..349f46064 100644 --- a/app/src/main/java/io/github/wulkanowy/api/grades/SubjectsList.java +++ b/app/src/main/java/io/github/wulkanowy/api/grades/SubjectsList.java @@ -21,7 +21,7 @@ public class SubjectsList { this.snp = snp; } - public List getAll() throws IOException, LoginErrorException { + public List getAll() throws IOException { Document subjectPage = snp.getSnPPageDocument(subjectsPageUrl); Elements rows = subjectPage.select(".ocenyZwykle-table > tbody > tr"); diff --git a/app/src/main/java/io/github/wulkanowy/api/login/Login.java b/app/src/main/java/io/github/wulkanowy/api/login/Login.java index 23ce6e9cb..dbed792c5 100644 --- a/app/src/main/java/io/github/wulkanowy/api/login/Login.java +++ b/app/src/main/java/io/github/wulkanowy/api/login/Login.java @@ -1,8 +1,9 @@ package io.github.wulkanowy.api.login; -import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; +import org.jsoup.parser.Parser; +import org.jsoup.select.Elements; import java.io.IOException; @@ -11,12 +12,10 @@ import io.github.wulkanowy.api.Cookies; public class Login extends Api { - private String loginPageUrl = "https://cufs.vulcan.net.pl/{symbol}/Account/LogOn"; - - private String certificatePageUrl = "https://cufs.vulcan.net.pl/{symbol}" - + "/FS/LS?wa=wsignin1.0&wtrealm=https://uonetplus.vulcan.net.pl/{symbol}" - + "/LoginEndpoint.aspx&wctx=https://uonetplus.vulcan.net.pl/{symbol}" - + "/LoginEndpoint.aspx"; + private String loginPageUrl = "https://cufs.vulcan.net.pl/{symbol}/Account/LogOn" + + "?ReturnUrl=%2F{symbol}%2FFS%2FLS%3Fwa%3Dwsignin1.0%26wtrealm%3D" + + "https%253a%252f%252fuonetplus.vulcan.net.pl%252f{symbol}%252fLoginEndpoint.aspx%26wctx%3D" + + "https%253a%252f%252fuonetplus.vulcan.net.pl%252f{symbol}%252fLoginEndpoint.aspx"; private String loginEndpointPageUrl = "https://uonetplus.vulcan.net.pl/{symbol}/LoginEndpoint.aspx"; @@ -25,62 +24,48 @@ public class Login extends Api { this.cookies = cookies; } - public boolean login(String email, String password, String symbol) - throws BadCredentialsException, LoginErrorException, AccountPermissionException { - try { - sendCredentials(email, password, symbol); - String[] certificate = getCertificateData(symbol); - sendCertificate(certificate[0], certificate[1], symbol); - } catch (IOException e) { - throw new LoginErrorException(); - } - - return true; + public String getLoginPageUrl() { + return loginPageUrl; } - private void sendCredentials(String email, String password, String symbol) + public String getLoginEndpointPageUrl() { + return loginEndpointPageUrl; + } + + public String login(String email, String password, String symbol) + throws BadCredentialsException, LoginErrorException, AccountPermissionException, IOException { + String certificate = sendCredentials(email, password, symbol); + + return sendCertificate(certificate, symbol); + } + + public String sendCredentials(String email, String password, String symbol) throws IOException, BadCredentialsException { - loginPageUrl = loginPageUrl.replace("{symbol}", symbol); + loginPageUrl = getLoginPageUrl().replace("{symbol}", symbol); - Connection.Response response = Jsoup.connect(loginPageUrl) - .data("LoginName", email) - .data("Password", password) - .method(Connection.Method.POST) - .execute(); + Document html = postPageByUrl(loginPageUrl, new String[][]{ + {"LoginName", email}, + {"Password", password} + }); - setCookies(response.cookies()); - Document document = response.parse(); - - if (null != document.select(".ErrorMessage").first()) { + if (null != html.select(".ErrorMessage").first()) { throw new BadCredentialsException(); } + + return html.select("input[name=wresult]").attr("value"); } - private String[] getCertificateData(String symbol) throws IOException { - certificatePageUrl = certificatePageUrl.replace("{symbol}", symbol); - - Document certificatePage = getPageByUrl(certificatePageUrl); - - return new String[]{ - certificatePage.select("input[name=wa]").attr("value"), - certificatePage.select("input[name=wresult]").attr("value") - }; - } - - private void sendCertificate(String protocolVersion, String certificate, String symbol) + public String sendCertificate(String certificate, String defaultSymbol) throws IOException, LoginErrorException, AccountPermissionException { - loginEndpointPageUrl = loginEndpointPageUrl.replace("{symbol}", symbol); + String symbol = findSymbol(defaultSymbol, certificate); - Connection.Response response = Jsoup.connect(loginEndpointPageUrl) - .data("wa", protocolVersion) - .data("wresult", certificate) - .cookies(getCookies()) - .followRedirects(true) - .method(Connection.Method.POST) - .execute(); + loginEndpointPageUrl = getLoginEndpointPageUrl() + .replace("{symbol}", symbol); - addCookies(response.cookies()); - Document html = response.parse(); + Document html = postPageByUrl(loginEndpointPageUrl, new String[][]{ + {"wa", "wsignin1.0"}, + {"wresult", certificate} + }); if (html.getElementsByTag("title").text().equals("Logowanie")) { throw new AccountPermissionException(); @@ -89,5 +74,26 @@ public class Login extends Api { if (!html.select("title").text().equals("Uonet+")) { throw new LoginErrorException(); } + + return symbol; + } + + public String findSymbol(String symbol, String certificate) { + if ("Default".equals(symbol)) { + return findSymbolInCertificate(certificate); + } + + return symbol; + } + + public String findSymbolInCertificate(String certificate) { + Elements els = Jsoup.parse(certificate.replaceAll(":", ""), "", Parser.xmlParser()) + .select("[AttributeName=\"UserInstance\"] samlAttributeValue"); + + if (0 == els.size()) { + return ""; + } + + return els.get(1).text(); } } diff --git a/app/src/main/java/io/github/wulkanowy/api/notes/AchievementsList.java b/app/src/main/java/io/github/wulkanowy/api/notes/AchievementsList.java index b4421cda1..52a4bbcd4 100644 --- a/app/src/main/java/io/github/wulkanowy/api/notes/AchievementsList.java +++ b/app/src/main/java/io/github/wulkanowy/api/notes/AchievementsList.java @@ -22,7 +22,7 @@ public class AchievementsList { this.snp = snp; } - public List getAllAchievements() throws LoginErrorException, IOException { + public List getAllAchievements() throws IOException { Element pageFragment = snp.getSnPPageDocument(notesPageUrl) .select(".mainContainer > div").get(1); Elements items = pageFragment.select("article"); diff --git a/app/src/main/java/io/github/wulkanowy/api/notes/NotesList.java b/app/src/main/java/io/github/wulkanowy/api/notes/NotesList.java index 3ae52bd45..5d1e920de 100644 --- a/app/src/main/java/io/github/wulkanowy/api/notes/NotesList.java +++ b/app/src/main/java/io/github/wulkanowy/api/notes/NotesList.java @@ -22,7 +22,7 @@ public class NotesList { this.snp = snp; } - public List getAllNotes() throws LoginErrorException, IOException { + public List getAllNotes() throws IOException { Element pageFragment = snp.getSnPPageDocument(notesPageUrl) .select(".mainContainer > div").get(0); Elements items = pageFragment.select("article"); diff --git a/app/src/main/java/io/github/wulkanowy/api/school/SchoolInfo.java b/app/src/main/java/io/github/wulkanowy/api/school/SchoolInfo.java index c479a7309..459e232c1 100644 --- a/app/src/main/java/io/github/wulkanowy/api/school/SchoolInfo.java +++ b/app/src/main/java/io/github/wulkanowy/api/school/SchoolInfo.java @@ -17,7 +17,7 @@ public class SchoolInfo { this.snp = snp; } - public SchoolData getSchoolData() throws IOException, LoginErrorException { + public SchoolData getSchoolData() throws IOException { Element e = snp.getSnPPageDocument(schoolPageUrl) .select(".mainContainer > article").get(0); diff --git a/app/src/main/java/io/github/wulkanowy/api/school/TeachersData.java b/app/src/main/java/io/github/wulkanowy/api/school/TeachersData.java index 73d88452f..7d41162e5 100644 --- a/app/src/main/java/io/github/wulkanowy/api/school/TeachersData.java +++ b/app/src/main/java/io/github/wulkanowy/api/school/TeachersData.java @@ -3,8 +3,11 @@ package io.github.wulkanowy.api.school; import java.util.List; public class TeachersData { + private String className = ""; + private String[] classTeacher; + private List subjects; public String getClassName() { diff --git a/app/src/main/java/io/github/wulkanowy/api/school/TeachersInfo.java b/app/src/main/java/io/github/wulkanowy/api/school/TeachersInfo.java index 236b6f832..fd17d21de 100644 --- a/app/src/main/java/io/github/wulkanowy/api/school/TeachersInfo.java +++ b/app/src/main/java/io/github/wulkanowy/api/school/TeachersInfo.java @@ -21,7 +21,7 @@ public class TeachersInfo { this.snp = snp; } - public TeachersData getTeachersData() throws IOException, LoginErrorException { + public TeachersData getTeachersData() throws IOException { Document doc = snp.getSnPPageDocument(schoolPageUrl); Elements rows = doc.select(".mainContainer > table tbody tr"); String description = doc.select(".mainContainer > p").first().text(); diff --git a/app/src/main/java/io/github/wulkanowy/api/timetable/Timetable.java b/app/src/main/java/io/github/wulkanowy/api/timetable/Timetable.java index 21a312c76..a00a6e877 100644 --- a/app/src/main/java/io/github/wulkanowy/api/timetable/Timetable.java +++ b/app/src/main/java/io/github/wulkanowy/api/timetable/Timetable.java @@ -25,7 +25,7 @@ public class Timetable { return getWeekTable(""); } - public Week getWeekTable(String tick) throws IOException, LoginErrorException { + public Week getWeekTable(String tick) throws IOException { Element table = snp.getSnPPageDocument(timetablePageUrl + tick) .select(".mainContainer .presentData").first(); diff --git a/app/src/main/java/io/github/wulkanowy/api/user/BasicInformation.java b/app/src/main/java/io/github/wulkanowy/api/user/BasicInformation.java index a75c44018..7c93cb866 100644 --- a/app/src/main/java/io/github/wulkanowy/api/user/BasicInformation.java +++ b/app/src/main/java/io/github/wulkanowy/api/user/BasicInformation.java @@ -20,7 +20,7 @@ public class BasicInformation { this.snp = snp; } - public Document getStudentDataPageDocument() throws IOException, LoginErrorException { + public Document getStudentDataPageDocument() throws IOException { if (null == studentDataPageDocument) { studentDataPageDocument = snp.getSnPPageDocument(studentDataPageUrl); } diff --git a/app/src/main/java/io/github/wulkanowy/api/user/FamilyInformation.java b/app/src/main/java/io/github/wulkanowy/api/user/FamilyInformation.java index 66ecc59e1..5315a5c5b 100644 --- a/app/src/main/java/io/github/wulkanowy/api/user/FamilyInformation.java +++ b/app/src/main/java/io/github/wulkanowy/api/user/FamilyInformation.java @@ -16,11 +16,11 @@ public class FamilyInformation { private String studentDataPageUrl = "Uczen.mvc/DanePodstawowe"; - public FamilyInformation(StudentAndParent snp) throws IOException, LoginErrorException { + public FamilyInformation(StudentAndParent snp) { this.snp = snp; } - public List getFamilyMembers() throws IOException, LoginErrorException { + public List getFamilyMembers() throws IOException { Elements membersElements = snp.getSnPPageDocument(studentDataPageUrl) .select(".mainContainer > article:nth-of-type(n+4)"); diff --git a/app/src/main/java/io/github/wulkanowy/dao/entities/Account.java b/app/src/main/java/io/github/wulkanowy/dao/entities/Account.java index 6f960cd70..0102a8e8f 100644 --- a/app/src/main/java/io/github/wulkanowy/dao/entities/Account.java +++ b/app/src/main/java/io/github/wulkanowy/dao/entities/Account.java @@ -27,6 +27,9 @@ public class Account { @Property(nameInDb = "SYMBOL") private String symbol; + @Property(nameInDb = "SNPID") + private String snpId; + @ToMany(referencedJoinProperty = "userId") private List subjectList; @@ -45,14 +48,15 @@ public class Account { @Generated(hash = 335469827) private transient AccountDao myDao; - @Generated(hash = 1514643300) - public Account(Long id, String name, String email, String password, - String symbol) { + @Generated(hash = 735765217) + public Account(Long id, String name, String email, String password, String symbol, + String snpId) { this.id = id; this.name = name; this.email = email; this.password = password; this.symbol = symbol; + this.snpId = snpId; } @Generated(hash = 882125521) @@ -104,6 +108,15 @@ public class Account { return this; } + public String getSnpId() { + return this.snpId; + } + + public Account setSnpId(String snpId) { + this.snpId = snpId; + return this; + } + /** * To-many relationship, resolved on first access (and after reset). * Changes to to-many relations are not persisted, make changes to the target entity. diff --git a/app/src/main/java/io/github/wulkanowy/dao/entities/Grade.java b/app/src/main/java/io/github/wulkanowy/dao/entities/Grade.java index 60100053e..03b4cc136 100644 --- a/app/src/main/java/io/github/wulkanowy/dao/entities/Grade.java +++ b/app/src/main/java/io/github/wulkanowy/dao/entities/Grade.java @@ -355,9 +355,7 @@ public class Grade implements Parcelable { myDao.update(this); } - /** - * called by internal mechanisms, do not call yourself. - */ + /** called by internal mechanisms, do not call yourself. */ @Generated(hash = 1187286414) public void __setDaoSession(DaoSession daoSession) { this.daoSession = daoSession; diff --git a/app/src/main/java/io/github/wulkanowy/services/VulcanSynchronization.java b/app/src/main/java/io/github/wulkanowy/services/VulcanSynchronization.java index d4a76f23a..5ce801327 100644 --- a/app/src/main/java/io/github/wulkanowy/services/VulcanSynchronization.java +++ b/app/src/main/java/io/github/wulkanowy/services/VulcanSynchronization.java @@ -9,11 +9,10 @@ import io.github.wulkanowy.api.Vulcan; import io.github.wulkanowy.api.login.AccountPermissionException; import io.github.wulkanowy.api.login.BadCredentialsException; import io.github.wulkanowy.api.login.LoginErrorException; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; import io.github.wulkanowy.dao.entities.DaoSession; import io.github.wulkanowy.security.CryptoException; import io.github.wulkanowy.services.jobs.VulcanJobHelper; -import io.github.wulkanowy.services.synchronisation.AccountSynchronisation; +import io.github.wulkanowy.services.synchronisation.AccountAuthorization; import io.github.wulkanowy.services.synchronisation.GradesSynchronisation; import io.github.wulkanowy.services.synchronisation.SubjectsSynchronisation; @@ -26,18 +25,10 @@ public class VulcanSynchronization { } public void loginCurrentUser(Context context, DaoSession daoSession, Vulcan vulcan) - throws CryptoException, BadCredentialsException, AccountPermissionException, IOException, LoginErrorException { + throws CryptoException, BadCredentialsException, AccountPermissionException, LoginErrorException, IOException { - AccountSynchronisation accountSynchronisation = new AccountSynchronisation(); - loginSession = accountSynchronisation.loginCurrentUser(context, daoSession, vulcan); - } - - public void loginNewUser(String email, String password, String symbol, - Context context, DaoSession daoSession, Vulcan vulcan) - throws BadCredentialsException, NotLoggedInErrorException, AccountPermissionException, IOException, CryptoException { - - AccountSynchronisation accountSynchronisation = new AccountSynchronisation(); - loginSession = accountSynchronisation.loginNewUser(email, password, symbol, context, daoSession, vulcan); + AccountAuthorization accountAuthorization = new AccountAuthorization(context, daoSession, vulcan); + loginSession = accountAuthorization.loginCurrentUser(); } public boolean syncGrades() { diff --git a/app/src/main/java/io/github/wulkanowy/services/synchronisation/AccountSynchronisation.java b/app/src/main/java/io/github/wulkanowy/services/synchronisation/AccountAuthorization.java similarity index 51% rename from app/src/main/java/io/github/wulkanowy/services/synchronisation/AccountSynchronisation.java rename to app/src/main/java/io/github/wulkanowy/services/synchronisation/AccountAuthorization.java index c4393d577..5c7ff62dd 100644 --- a/app/src/main/java/io/github/wulkanowy/services/synchronisation/AccountSynchronisation.java +++ b/app/src/main/java/io/github/wulkanowy/services/synchronisation/AccountAuthorization.java @@ -1,7 +1,6 @@ package io.github.wulkanowy.services.synchronisation; import android.content.Context; -import android.content.SharedPreferences; import android.util.Log; import java.io.IOException; @@ -10,8 +9,6 @@ import io.github.wulkanowy.api.Vulcan; import io.github.wulkanowy.api.login.AccountPermissionException; import io.github.wulkanowy.api.login.BadCredentialsException; import io.github.wulkanowy.api.login.LoginErrorException; -import io.github.wulkanowy.api.login.NotLoggedInErrorException; -import io.github.wulkanowy.api.user.PersonalData; import io.github.wulkanowy.dao.entities.Account; import io.github.wulkanowy.dao.entities.AccountDao; import io.github.wulkanowy.dao.entities.DaoSession; @@ -20,9 +17,21 @@ import io.github.wulkanowy.security.Safety; import io.github.wulkanowy.services.LoginSession; import io.github.wulkanowy.services.jobs.VulcanJobHelper; -public class AccountSynchronisation { +public class AccountAuthorization { - public LoginSession loginCurrentUser(Context context, DaoSession daoSession, Vulcan vulcan) throws CryptoException, + private final Context context; + + private final DaoSession daoSession; + + private final Vulcan vulcan; + + public AccountAuthorization(Context context, DaoSession daoSession, Vulcan vulcan) { + this.context = context; + this.daoSession = daoSession; + this.vulcan = vulcan; + } + + public LoginSession loginCurrentUser() throws CryptoException, BadCredentialsException, AccountPermissionException, IOException, LoginErrorException { AccountDao accountDao = daoSession.getAccountDao(); @@ -38,7 +47,8 @@ public class AccountSynchronisation { vulcan.login( account.getEmail(), safety.decrypt(account.getEmail(), account.getPassword()), - account.getSymbol() + account.getSymbol(), + account.getSnpId() ); return new LoginSession() @@ -50,36 +60,4 @@ public class AccountSynchronisation { throw new IOException("Can't find user with index 0"); } } - - public LoginSession loginNewUser(String email, String password, String symbol, Context context, DaoSession daoSession, Vulcan vulcan) - throws BadCredentialsException, NotLoggedInErrorException, AccountPermissionException, IOException, CryptoException { - - long userId; - - vulcan.login(email, password, symbol); - - PersonalData personalData = vulcan.getBasicInformation().getPersonalData(); - AccountDao accountDao = daoSession.getAccountDao(); - Safety safety = new Safety(); - - Account account = new Account() - .setName(personalData.getFirstAndLastName()) - .setEmail(email) - .setPassword(safety.encrypt(email, password, context)) - .setSymbol(symbol); - - userId = accountDao.insert(account); - - Log.d(VulcanJobHelper.DEBUG_TAG, "Login and save new user id=" + String.valueOf(userId)); - - SharedPreferences sharedPreferences = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putLong("userId", userId); - editor.apply(); - - return new LoginSession() - .setVulcan(vulcan) - .setUserId(userId) - .setDaoSession(daoSession); - } } diff --git a/app/src/main/java/io/github/wulkanowy/services/synchronisation/AccountRegistration.java b/app/src/main/java/io/github/wulkanowy/services/synchronisation/AccountRegistration.java new file mode 100644 index 000000000..4bfc7817c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/synchronisation/AccountRegistration.java @@ -0,0 +1,82 @@ +package io.github.wulkanowy.services.synchronisation; + +import android.content.Context; +import android.content.SharedPreferences; + +import java.io.IOException; + +import io.github.wulkanowy.api.Vulcan; +import io.github.wulkanowy.api.login.AccountPermissionException; +import io.github.wulkanowy.api.login.BadCredentialsException; +import io.github.wulkanowy.api.login.Login; +import io.github.wulkanowy.api.login.NotLoggedInErrorException; +import io.github.wulkanowy.dao.entities.Account; +import io.github.wulkanowy.dao.entities.AccountDao; +import io.github.wulkanowy.dao.entities.DaoSession; +import io.github.wulkanowy.security.CryptoException; +import io.github.wulkanowy.security.Safety; +import io.github.wulkanowy.services.LoginSession; +import io.github.wulkanowy.services.jobs.GradeJob; + +public class AccountRegistration { + + + private final Login login; + + private final Vulcan vulcan; + + private final String email; + + private final String password; + + private final String symbol; + + public AccountRegistration(Login login, Vulcan vulcan, String email, String password, String symbol) { + this.login = login; + this.vulcan = vulcan; + this.email = email; + this.password = password; + this.symbol = symbol; + } + + public String connect() + throws BadCredentialsException, IOException { + return login.sendCredentials(email, password, symbol); + } + + public LoginSession login(Context context, DaoSession daoSession, String certificate) + throws NotLoggedInErrorException, AccountPermissionException, IOException, CryptoException { + + long userId; + + String realSymbol = login.sendCertificate(certificate, symbol); + + vulcan.login(login.getCookiesObject(), realSymbol); + + AccountDao accountDao = daoSession.getAccountDao(); + Safety safety = new Safety(); + Account account = new Account() + .setName(vulcan.getBasicInformation().getPersonalData().getFirstAndLastName()) + .setEmail(email) + .setPassword(safety.encrypt(email, password, context)) + .setSymbol(vulcan.getStudentAndParent().getSymbol()) + .setSnpId(vulcan.getStudentAndParent().getId()); + + userId = accountDao.insert(account); + + SharedPreferences sharedPreferences = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putLong("userId", userId); + editor.apply(); + + return new LoginSession() + .setVulcan(vulcan) + .setUserId(userId) + .setDaoSession(daoSession); + } + + public void scheduleSynchronization(Context context) { + GradeJob gradesSync = new GradeJob(); + gradesSync.scheduledJob(context); + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/synchronisation/SubjectsSynchronisation.java b/app/src/main/java/io/github/wulkanowy/services/synchronisation/SubjectsSynchronisation.java index 38f604a1b..cb2d3a9b8 100644 --- a/app/src/main/java/io/github/wulkanowy/services/synchronisation/SubjectsSynchronisation.java +++ b/app/src/main/java/io/github/wulkanowy/services/synchronisation/SubjectsSynchronisation.java @@ -18,7 +18,7 @@ import io.github.wulkanowy.utilities.ConversionVulcanObject; public class SubjectsSynchronisation { public void sync(LoginSession loginSession) throws IOException, - ParseException, NotLoggedInErrorException { + NotLoggedInErrorException { SubjectsList subjectsList = loginSession.getVulcan().getSubjectsList(); SubjectDao subjectDao = loginSession.getDaoSession().getSubjectDao(); diff --git a/app/src/main/res/drawable/ic_arrow_down.xml b/app/src/main/res/drawable/ic_arrow_down.xml new file mode 100644 index 000000000..5a609d656 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_down.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 000000000..b619e0385 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +