From 09f3191ca00c01cc6d48ca573d7707c8a7de6b1f Mon Sep 17 00:00:00 2001 From: Bruno Mendes Date: Sat, 23 Sep 2023 12:40:53 +0100 Subject: [PATCH] Use same key for encryption purposes --- .../background_workers/notifications.dart | 4 ++ .../local_storage/app_shared_preferences.dart | 38 +++++++++---------- .../controller/networking/network_router.dart | 7 ++++ uni/lib/main.dart | 4 +- .../model/providers/lazy/exam_provider.dart | 12 +++--- .../providers/lazy/lecture_provider.dart | 8 ++-- .../providers/startup/profile_provider.dart | 8 ++-- .../providers/startup/session_provider.dart | 5 +++ .../view/common_widgets/page_transition.dart | 9 ----- uni/lib/view/login/login.dart | 2 + uni/lib/view/terms_and_condition_dialog.dart | 7 +--- uni/test/integration/src/exams_page_test.dart | 5 +-- .../integration/src/schedule_page_test.dart | 3 +- .../unit/providers/exams_provider_test.dart | 17 ++++----- .../unit/providers/lecture_provider_test.dart | 6 +-- 15 files changed, 65 insertions(+), 70 deletions(-) diff --git a/uni/lib/controller/background_workers/notifications.dart b/uni/lib/controller/background_workers/notifications.dart index f6c7f52f8..b09b0365a 100644 --- a/uni/lib/controller/background_workers/notifications.dart +++ b/uni/lib/controller/background_workers/notifications.dart @@ -71,6 +71,10 @@ class NotificationManager { final userInfo = await AppSharedPreferences.getPersistentUserInfo(); final faculties = await AppSharedPreferences.getUserFaculties(); + if (userInfo == null || faculties.isEmpty) { + return; + } + final session = await NetworkRouter.login( userInfo.item1, userInfo.item2, diff --git a/uni/lib/controller/local_storage/app_shared_preferences.dart b/uni/lib/controller/local_storage/app_shared_preferences.dart index d5da1a7c4..cf447d340 100644 --- a/uni/lib/controller/local_storage/app_shared_preferences.dart +++ b/uni/lib/controller/local_storage/app_shared_preferences.dart @@ -14,6 +14,10 @@ import 'package:uni/utils/favorite_widget_type.dart'; /// This database stores the user's student number, password and favorite /// widgets. class AppSharedPreferences { + static final iv = encrypt.IV.fromLength(16); + static final key = + encrypt.Key.fromBase64('DT3/GTNYldhwOD3ZbpVLoAwA/mncsN7U7sJxfFn3y0A='); + static const lastUpdateTimeKeySuffix = '_last_update_time'; static const String userNumber = 'user_number'; static const String userPw = 'user_password'; @@ -24,10 +28,6 @@ class AppSharedPreferences { 'tuition_notification_toogle'; static const String themeMode = 'theme_mode'; static const String locale = 'app_locale'; - static const int keyLength = 32; - static const int ivLength = 16; - static final iv = encrypt.IV.fromLength(ivLength); - static const String favoriteCards = 'favorite_cards'; static final List defaultFavoriteCards = [ FavoriteWidgetType.schedule, @@ -149,9 +149,12 @@ class AppSharedPreferences { /// * the first element in the tuple is the user's student number. /// * the second element in the tuple is the user's password, in plain text /// format. - static Future> getPersistentUserInfo() async { + static Future?> getPersistentUserInfo() async { final userNum = await getUserNumber(); final userPass = await getUserPassword(); + if (userNum == null || userPass == null) { + return null; + } return Tuple2(userNum, userPass); } @@ -164,22 +167,16 @@ class AppSharedPreferences { } /// Returns the user's student number. - static Future getUserNumber() async { + static Future getUserNumber() async { final prefs = await SharedPreferences.getInstance(); - return prefs.getString(userNumber) ?? - ''; // empty string for the case it does not exist + return prefs.getString(userNumber); } /// Returns the user's password, in plain text format. - static Future getUserPassword() async { + static Future getUserPassword() async { final prefs = await SharedPreferences.getInstance(); - var pass = prefs.getString(userPw) ?? ''; - - if (pass != '') { - pass = decode(pass); - } - - return pass; + final password = prefs.getString(userPw); + return password != null ? decode(password) : null; } /// Replaces the user's favorite widgets with [newFavorites]. @@ -270,15 +267,18 @@ class AppSharedPreferences { } /// Decrypts [base64Text]. - static String decode(String base64Text) { + static String? decode(String base64Text) { final encrypter = _createEncrypter(); - return encrypter.decrypt64(base64Text, iv: iv); + try { + return encrypter.decrypt64(base64Text, iv: iv); + } catch (e) { + return null; + } } /// Creates an [encrypt.Encrypter] for encrypting and decrypting the user's /// password. static encrypt.Encrypter _createEncrypter() { - final key = encrypt.Key.fromLength(keyLength); return encrypt.Encrypter(encrypt.AES(key)); } diff --git a/uni/lib/controller/networking/network_router.dart b/uni/lib/controller/networking/network_router.dart index ce60f4a68..a72f59b98 100644 --- a/uni/lib/controller/networking/network_router.dart +++ b/uni/lib/controller/networking/network_router.dart @@ -71,6 +71,7 @@ class NetworkRouter { faculties, persistentSession: persistentSession, ); + if (session == null) { Logger().e('Login failed: user not authenticated'); return null; @@ -90,6 +91,12 @@ class NetworkRouter { static Future reLoginFromSession(Session session) async { final username = session.username; final password = await AppSharedPreferences.getUserPassword(); + + if (password == null) { + Logger().e('Re-login failed: password not found'); + return null; + } + final faculties = session.faculties; final persistentSession = session.persistentSession; diff --git a/uni/lib/main.dart b/uni/lib/main.dart index 6f2e29a30..407d18030 100644 --- a/uni/lib/main.dart +++ b/uni/lib/main.dart @@ -52,10 +52,8 @@ SentryEvent? beforeSend(SentryEvent event) { Future firstRoute() async { final userPersistentInfo = await AppSharedPreferences.getPersistentUserInfo(); - final userName = userPersistentInfo.item1; - final password = userPersistentInfo.item2; - if (userName != '' && password != '') { + if (userPersistentInfo != null) { return '/${DrawerItem.navPersonalArea.title}'; } diff --git a/uni/lib/model/providers/lazy/exam_provider.dart b/uni/lib/model/providers/lazy/exam_provider.dart index 562424f80..4327df8b3 100644 --- a/uni/lib/model/providers/lazy/exam_provider.dart +++ b/uni/lib/model/providers/lazy/exam_provider.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:collection'; -import 'package:tuple/tuple.dart'; import 'package:uni/controller/fetchers/exam_fetcher.dart'; import 'package:uni/controller/local_storage/app_exams_database.dart'; import 'package:uni/controller/local_storage/app_shared_preferences.dart'; @@ -42,27 +41,28 @@ class ExamProvider extends StateProviderNotifier { Future loadFromRemote(Session session, Profile profile) async { await fetchUserExams( ParserExams(), - await AppSharedPreferences.getPersistentUserInfo(), profile, session, profile.courseUnits, + persistentSession: + (await AppSharedPreferences.getPersistentUserInfo()) != null, ); } Future fetchUserExams( ParserExams parserExams, - Tuple2 userPersistentInfo, Profile profile, Session session, - List userUcs, - ) async { + List userUcs, { + required bool persistentSession, + }) async { try { final exams = await ExamFetcher(profile.courses, userUcs) .extractExams(session, parserExams); exams.sort((exam1, exam2) => exam1.begin.compareTo(exam2.begin)); - if (userPersistentInfo.item1 != '' && userPersistentInfo.item2 != '') { + if (persistentSession) { await AppExamsDatabase().saveNewExams(exams); } diff --git a/uni/lib/model/providers/lazy/lecture_provider.dart b/uni/lib/model/providers/lazy/lecture_provider.dart index a9f832a5f..fd5789ef7 100644 --- a/uni/lib/model/providers/lazy/lecture_provider.dart +++ b/uni/lib/model/providers/lazy/lecture_provider.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:collection'; -import 'package:tuple/tuple.dart'; import 'package:uni/controller/fetchers/schedule_fetcher/schedule_fetcher.dart'; import 'package:uni/controller/fetchers/schedule_fetcher/schedule_fetcher_api.dart'; import 'package:uni/controller/fetchers/schedule_fetcher/schedule_fetcher_html.dart'; @@ -30,23 +29,24 @@ class LectureProvider extends StateProviderNotifier { @override Future loadFromRemote(Session session, Profile profile) async { await fetchUserLectures( - await AppSharedPreferences.getPersistentUserInfo(), session, profile, + persistentSession: + (await AppSharedPreferences.getPersistentUserInfo()) != null, ); } Future fetchUserLectures( - Tuple2 userPersistentInfo, Session session, Profile profile, { + required bool persistentSession, ScheduleFetcher? fetcher, }) async { try { final lectures = await getLecturesFromFetcherOrElse(fetcher, session, profile); - if (userPersistentInfo.item1 != '' && userPersistentInfo.item2 != '') { + if (persistentSession) { final db = AppLecturesDatabase(); await db.saveNewLectures(lectures); } diff --git a/uni/lib/model/providers/startup/profile_provider.dart b/uni/lib/model/providers/startup/profile_provider.dart index 81d386fad..c6f05d31a 100644 --- a/uni/lib/model/providers/startup/profile_provider.dart +++ b/uni/lib/model/providers/startup/profile_provider.dart @@ -75,7 +75,7 @@ class ProfileProvider extends StateProviderNotifier { final userPersistentInfo = await AppSharedPreferences.getPersistentUserInfo(); - if (userPersistentInfo.item1 != '' && userPersistentInfo.item2 != '') { + if (userPersistentInfo != null) { final profileDb = AppUserDataDatabase(); await profileDb.saveUserFees(feesBalance, feesLimit); } @@ -95,7 +95,7 @@ class ProfileProvider extends StateProviderNotifier { final userPersistentInfo = await AppSharedPreferences.getPersistentUserInfo(); - if (userPersistentInfo.item1 != '' && userPersistentInfo.item2 != '') { + if (userPersistentInfo != null) { final profileDb = AppUserDataDatabase(); await profileDb.saveUserPrintBalance(printBalance); } @@ -119,7 +119,7 @@ class ProfileProvider extends StateProviderNotifier { final userPersistentInfo = await AppSharedPreferences.getPersistentUserInfo(); - if (userPersistentInfo.item1 != '' && userPersistentInfo.item2 != '') { + if (userPersistentInfo != null) { // Course units are saved later, so we don't it here final profileDb = AppUserDataDatabase(); await profileDb.insertUserData(_profile); @@ -144,7 +144,7 @@ class ProfileProvider extends StateProviderNotifier { final userPersistentInfo = await AppSharedPreferences.getPersistentUserInfo(); - if (userPersistentInfo.item1 != '' && userPersistentInfo.item2 != '') { + if (userPersistentInfo != null) { final coursesDb = AppCoursesDatabase(); await coursesDb.saveNewCourses(courses); diff --git a/uni/lib/model/providers/startup/session_provider.dart b/uni/lib/model/providers/startup/session_provider.dart index e4b006ea6..6131432dd 100644 --- a/uni/lib/model/providers/startup/session_provider.dart +++ b/uni/lib/model/providers/startup/session_provider.dart @@ -35,6 +35,11 @@ class SessionProvider extends StateProviderNotifier { Future loadFromStorage() async { final userPersistentInfo = await AppSharedPreferences.getPersistentUserInfo(); + + if (userPersistentInfo == null) { + return; + } + final userName = userPersistentInfo.item1; final password = userPersistentInfo.item2; diff --git a/uni/lib/view/common_widgets/page_transition.dart b/uni/lib/view/common_widgets/page_transition.dart index 494f8097f..2f3581a0d 100644 --- a/uni/lib/view/common_widgets/page_transition.dart +++ b/uni/lib/view/common_widgets/page_transition.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; - -import 'package:uni/controller/local_storage/app_shared_preferences.dart'; import 'package:uni/view/navigation_service.dart'; import 'package:uni/view/terms_and_condition_dialog.dart'; @@ -49,16 +47,9 @@ class PageTransition { static Future requestTermsAndConditionsAcceptanceIfNeeded( BuildContext context, ) async { - final userPersistentInfo = - await AppSharedPreferences.getPersistentUserInfo(); - final userName = userPersistentInfo.item1; - final password = userPersistentInfo.item2; - if (context.mounted) { final termsAcceptance = await TermsAndConditionDialog.buildIfTermsChanged( context, - userName, - password, ); switch (termsAcceptance) { diff --git a/uni/lib/view/login/login.dart b/uni/lib/view/login/login.dart index 7cea404ee..a7cd92df5 100644 --- a/uni/lib/view/login/login.dart +++ b/uni/lib/view/login/login.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:logger/logger.dart'; import 'package:provider/provider.dart'; import 'package:uni/generated/l10n.dart'; import 'package:uni/model/entities/login_exceptions.dart'; @@ -67,6 +68,7 @@ class LoginPageViewState extends State { } else if (error is WrongCredentialsException) { unawaited(ToastMessage.error(context, error.message)); } else { + Logger().e(error); unawaited(ToastMessage.error(context, S.of(context).failed_login)); } } diff --git a/uni/lib/view/terms_and_condition_dialog.dart b/uni/lib/view/terms_and_condition_dialog.dart index a1b1e2a8e..245cd0747 100644 --- a/uni/lib/view/terms_and_condition_dialog.dart +++ b/uni/lib/view/terms_and_condition_dialog.dart @@ -13,8 +13,6 @@ class TermsAndConditionDialog { static Future buildIfTermsChanged( BuildContext context, - String userName, - String password, ) async { final termsAreAccepted = await updateTermsAndConditionsAcceptancePreference(); @@ -22,8 +20,7 @@ class TermsAndConditionDialog { if (!termsAreAccepted) { final routeCompleter = Completer(); SchedulerBinding.instance.addPostFrameCallback( - (timestamp) => - _buildShowDialog(context, routeCompleter, userName, password), + (timestamp) => _buildShowDialog(context, routeCompleter), ); return routeCompleter.future; } @@ -34,8 +31,6 @@ class TermsAndConditionDialog { static Future _buildShowDialog( BuildContext context, Completer userTermsDecision, - String userName, - String password, ) { return showDialog( context: context, diff --git a/uni/test/integration/src/exams_page_test.dart b/uni/test/integration/src/exams_page_test.dart index 44622790c..03779534f 100644 --- a/uni/test/integration/src/exams_page_test.dart +++ b/uni/test/integration/src/exams_page_test.dart @@ -6,7 +6,6 @@ import 'package:http/http.dart' as http; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; import 'package:uni/controller/networking/network_router.dart'; import 'package:uni/controller/parsers/parser_exams.dart'; import 'package:uni/model/entities/course.dart'; @@ -82,10 +81,10 @@ void main() { await examProvider.fetchUserExams( ParserExams(), - const Tuple2('', ''), profile, Session(username: '', cookies: '', faculties: ['feup']), [sopeCourseUnit, sdisCourseUnit], + persistentSession: false, ); await tester.pumpAndSettle(); @@ -118,10 +117,10 @@ void main() { await examProvider.fetchUserExams( ParserExams(), - const Tuple2('', ''), profile, Session(username: '', cookies: '', faculties: ['feup']), [sopeCourseUnit, sdisCourseUnit], + persistentSession: false, ); await tester.pumpAndSettle(); diff --git a/uni/test/integration/src/schedule_page_test.dart b/uni/test/integration/src/schedule_page_test.dart index e775c86b2..0deb663d2 100644 --- a/uni/test/integration/src/schedule_page_test.dart +++ b/uni/test/integration/src/schedule_page_test.dart @@ -7,7 +7,6 @@ import 'package:http/http.dart' as http; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; import 'package:uni/controller/networking/network_router.dart'; import 'package:uni/model/entities/course.dart'; import 'package:uni/model/entities/profile.dart'; @@ -71,9 +70,9 @@ void main() { expect(find.byKey(const Key(scheduleSlotTimeKey2)), findsNothing); await scheduleProvider.fetchUserLectures( - const Tuple2('', ''), Session(username: '', cookies: '', faculties: ['feup']), profile, + persistentSession: false, ); await tester.tap(find.byKey(const Key('schedule-page-tab-2'))); diff --git a/uni/test/unit/providers/exams_provider_test.dart b/uni/test/unit/providers/exams_provider_test.dart index 2f4c33ddd..5c696a338 100644 --- a/uni/test/unit/providers/exams_provider_test.dart +++ b/uni/test/unit/providers/exams_provider_test.dart @@ -2,7 +2,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:tuple/tuple.dart'; import 'package:uni/controller/networking/network_router.dart'; import 'package:uni/controller/parsers/parser_exams.dart'; import 'package:uni/model/entities/course.dart'; @@ -61,8 +60,6 @@ void main() { 'feup', ); - const userPersistentInfo = Tuple2('', ''); - final profile = Profile()..courses = [Course(id: 7474)]; final session = Session(username: '', cookies: '', faculties: ['feup']); final userUcs = [sopeCourseUnit, sdisCourseUnit]; @@ -85,10 +82,10 @@ void main() { await provider.fetchUserExams( parserExams, - userPersistentInfo, profile, session, userUcs, + persistentSession: false, ); expect(provider.exams.isNotEmpty, true); @@ -102,10 +99,10 @@ void main() { await provider.fetchUserExams( parserExams, - userPersistentInfo, profile, session, userUcs, + persistentSession: false, ); expect(provider.status, RequestStatus.successful); @@ -132,10 +129,10 @@ When given three exams but one is to be parsed out, await provider.fetchUserExams( parserExams, - userPersistentInfo, profile, session, userUcs, + persistentSession: false, ); expect(provider.status, RequestStatus.successful); @@ -148,10 +145,10 @@ When given three exams but one is to be parsed out, await provider.fetchUserExams( parserExams, - userPersistentInfo, profile, session, userUcs, + persistentSession: false, ); expect(provider.status, RequestStatus.failed); }); @@ -174,10 +171,10 @@ When given three exams but one is to be parsed out, await provider.fetchUserExams( parserExams, - userPersistentInfo, profile, session, userUcs, + persistentSession: false, ); expect(provider.status, RequestStatus.successful); @@ -202,10 +199,10 @@ When given three exams but one is to be parsed out, await provider.fetchUserExams( parserExams, - userPersistentInfo, profile, session, userUcs, + persistentSession: false, ); expect(provider.status, RequestStatus.successful); @@ -230,10 +227,10 @@ When given three exams but one is to be parsed out, await provider.fetchUserExams( parserExams, - userPersistentInfo, profile, session, userUcs, + persistentSession: false, ); expect(provider.status, RequestStatus.successful); diff --git a/uni/test/unit/providers/lecture_provider_test.dart b/uni/test/unit/providers/lecture_provider_test.dart index 8498f01c2..77edb5e27 100644 --- a/uni/test/unit/providers/lecture_provider_test.dart +++ b/uni/test/unit/providers/lecture_provider_test.dart @@ -2,7 +2,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:tuple/tuple.dart'; import 'package:uni/controller/fetchers/schedule_fetcher/schedule_fetcher.dart'; import 'package:uni/controller/networking/network_router.dart'; import 'package:uni/model/entities/course.dart'; @@ -22,7 +21,6 @@ void main() { final fetcherMock = MockScheduleFetcher(); final mockClient = MockClient(); final mockResponse = MockResponse(); - const userPersistentInfo = Tuple2('', ''); final profile = Profile()..courses = [Course(id: 7474)]; final session = Session(username: '', cookies: '', faculties: ['feup']); final day = DateTime(2021, 06); @@ -66,10 +64,10 @@ void main() { .thenAnswer((_) async => [lecture1, lecture2]); await provider.fetchUserLectures( - userPersistentInfo, session, profile, fetcher: fetcherMock, + persistentSession: false, ); expect(provider.lectures, [lecture1, lecture2]); @@ -81,10 +79,10 @@ void main() { .thenAnswer((_) async => throw Exception('💥')); await provider.fetchUserLectures( - userPersistentInfo, session, profile, fetcher: fetcherMock, + persistentSession: false, ); expect(provider.status, RequestStatus.failed);