Added central user credentials management

This commit is contained in:
Elias Müller 2023-06-11 18:18:37 +02:00
parent 04c244503e
commit 1521056217
22 changed files with 207 additions and 159 deletions

View File

@ -3,13 +3,12 @@ import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../model/accountData.dart';
import 'autocompleteResponse.dart';
class AutocompleteApi {
Future<AutocompleteResponse> find(String query) async {
var preferences = await SharedPreferences.getInstance();
Map<String, dynamic> getParameters = {
"search": query,
"itemType": " ",
@ -22,7 +21,7 @@ class AutocompleteApi {
headers.putIfAbsent("Accept", () => "application/json");
headers.putIfAbsent("OCS-APIRequest", () => "true");
Uri endpoint = Uri.https("${preferences.getString("username")!}:${preferences.getString("password")!}@cloud.marianum-fulda.de", "/ocs/v2.php/core/autocomplete/get", getParameters);
Uri endpoint = Uri.https("${AccountData().buildHttpAuthString()}@cloud.marianum-fulda.de", "/ocs/v2.php/core/autocomplete/get", getParameters);
Response response = await http.get(endpoint, headers: headers);
if(response.statusCode != HttpStatus.ok) throw Exception("Api call failed with ${response.statusCode}: ${response.body}");

View File

@ -3,19 +3,17 @@ import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../model/accountData.dart';
import 'fileSharingApiParams.dart';
class FileSharingApi {
Future<void> share(FileSharingApiParams query) async {
var preferences = await SharedPreferences.getInstance();
Map<String, String> headers = {};
headers.putIfAbsent("Accept", () => "application/json");
headers.putIfAbsent("OCS-APIRequest", () => "true");
Uri endpoint = Uri.https("${preferences.getString("username")!}:${preferences.getString("password")!}@cloud.marianum-fulda.de", "/ocs/v2.php/apps/files_sharing/api/v1/shares", query.toJson().map((key, value) => MapEntry(key, value.toString())));
Uri endpoint = Uri.https("${AccountData().buildHttpAuthString()}@cloud.marianum-fulda.de", "/ocs/v2.php/apps/files_sharing/api/v1/shares", query.toJson().map((key, value) => MapEntry(key, value.toString())));
log("request file share");
Response response = await http.post(endpoint, headers: headers);

View File

@ -1,8 +1,8 @@
import 'dart:developer';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../../../model/accountData.dart';
import '../../apiError.dart';
import '../../apiParams.dart';
import '../../apiRequest.dart';
@ -32,9 +32,7 @@ abstract class TalkApi<T> extends ApiRequest {
getParameters?.update(key, (value) => value.toString());
});
SharedPreferences preferences = await SharedPreferences.getInstance();
Uri endpoint = Uri.https("${preferences.getString("username")!}:${preferences.getString("password")!}@cloud.marianum-fulda.de", "/ocs/v2.php/apps/spreed/api/$path", getParameters);
Uri endpoint = Uri.https("${AccountData().buildHttpAuthString()}@cloud.marianum-fulda.de", "/ocs/v2.php/apps/spreed/api/$path", getParameters);
headers ??= {};
headers?.putIfAbsent("Accept", () => "application/json");

View File

@ -1,6 +1,6 @@
import 'package:nextcloud/nextcloud.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../model/accountData.dart';
import '../../apiRequest.dart';
import '../../apiResponse.dart';
@ -17,14 +17,10 @@ abstract class WebdavApi<T> extends ApiRequest {
static Future<String> webdavConnectString = buildWebdavConnectString();
static Future<WebDavClient> establishWebdavConnection() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
return NextcloudClient("https://cloud.marianum-fulda.de/", username: preferences.getString("username"), password: preferences.getString("password"), loginName: preferences.getString("username")).webdav;
return NextcloudClient("https://cloud.marianum-fulda.de/", username: AccountData().getUsername(), password: AccountData().getPassword(), loginName: AccountData().getUsername()).webdav;
}
static Future<String> buildWebdavConnectString() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
return "https://${preferences.getString("username")}:${preferences.getString("password")}@cloud.marianum-fulda.de/remote.php/dav/files/${preferences.getString("username")}/";
return "https://${AccountData().buildHttpAuthString()}@cloud.marianum-fulda.de/remote.php/dav/files/${AccountData().getUsername()}/";
}
}

View File

@ -2,8 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../../model/accountData.dart';
import '../../webuntisApi.dart';
import 'authenticateParams.dart';
import 'authenticateResponse.dart';
@ -28,12 +27,10 @@ class Authenticate extends WebuntisApi {
static AuthenticateResponse? _lastResponse;
static Future<void> createSession() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
_lastResponse = await Authenticate(
AuthenticateParams(
user: preferences.getString("username")!,
password: preferences.getString("password")!,
user: AccountData().getUsername(),
password: AccountData().getPassword(),
)
).run();
}

View File

@ -22,14 +22,16 @@ class App extends StatefulWidget {
class _AppState extends State<App> {
int currentPage = 0;
late Timer refetchChats;
late Timer updateTimings;
@override
void initState() {
Timer.periodic(const Duration(seconds: 30), (Timer t) => setState((){}));
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Provider.of<ChatListProps>(context, listen: false).run();
});
updateTimings = Timer.periodic(const Duration(seconds: 30), (Timer t) => setState((){}));
refetchChats = Timer.periodic(const Duration(minutes: 1), (timer) {
if(!context.mounted) return;
Provider.of<ChatListProps>(context, listen: false).run();
@ -107,6 +109,7 @@ class _AppState extends State<App> {
@override
void dispose() {
refetchChats.cancel();
updateTimings.cancel();
super.dispose();
}
}

View File

@ -4,10 +4,10 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:jiffy/jiffy.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'app.dart';
import 'model/accountData.dart';
import 'model/accountModel.dart';
import 'model/chatList/chatListProps.dart';
import 'model/chatList/chatProps.dart';
@ -18,7 +18,7 @@ import 'storage/base/settingsProvider.dart';
import 'theming/darkAppTheme.dart';
import 'theming/lightAppTheme.dart';
import 'view/login/login.dart';
import 'widget/errorView.dart';
import 'widget/placeholderView.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
@ -27,7 +27,7 @@ Future<void> main() async {
SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List());
ErrorWidget.builder = (error) {
return ErrorView(icon: Icons.phonelink_erase_rounded, text: error.toString());
return PlaceholderView(icon: Icons.phonelink_erase_rounded, text: error.toString());
};
runApp(
@ -56,66 +56,54 @@ class Main extends StatefulWidget {
}
class _MainState extends State<Main> {
final Future<SharedPreferences> _storage = SharedPreferences.getInstance();
@override
void initState() {
super.initState();
Jiffy.setLocale("de");
_storage.then((SharedPreferences preferences) => preferences.getBool("loggedIn") ?? false).then((value) => {
if(value) {
Provider.of<AccountModel>(context, listen: false).login()
} else {
Provider.of<AccountModel>(context, listen: false).logout()
}
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
AccountData().waitForPopulation().then((value) {
Provider.of<AccountModel>(context, listen: false)
.setState(value ? AccountModelState.loggedIn : AccountModelState.loggedOut);
});
super.initState();
}
@override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
return Directionality(
textDirection: TextDirection.ltr,
child: Consumer<SettingsProvider>(
builder: (context, settings, child) {
return MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
...GlobalMaterialLocalizations.delegates,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: const [
Locale('de'),
Locale('en'),
],
locale: const Locale('de'),
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
...GlobalMaterialLocalizations.delegates,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: const [
Locale('de'),
Locale('en'),
],
locale: const Locale('de'),
title: 'Marianum Fulda',
themeMode: settings.val().appTheme,
theme: LightAppTheme.theme,
darkTheme: DarkAppTheme.theme,
title: 'Marianum Fulda',
themeMode: settings.val().appTheme,
theme: LightAppTheme.theme,
darkTheme: DarkAppTheme.theme,
home: FutureBuilder<SharedPreferences>(
future: _storage,
builder: (BuildContext context, AsyncSnapshot<SharedPreferences> snapshot) {
if(snapshot.hasData) {
return Consumer<AccountModel>(
builder: (context, accountModel, child) {
return accountModel.isLoggedIn ? const App() : const Login();
},
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
)
home: Consumer<AccountModel>(
builder: (context, accountModel, child) {
switch(accountModel.state) {
case AccountModelState.loggedIn: return const App();
case AccountModelState.loggedOut: return const Login();
case AccountModelState.undefined: return const PlaceholderView(icon: Icons.timer, text: "Daten werden geladen");
}
},
),
);
},
),

View File

@ -0,0 +1,78 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'accountModel.dart';
class AccountData {
static const _usernameField = "username";
static const _passwordField = "password";
static final AccountData _instance = AccountData._construct();
final Future<SharedPreferences> _storage = SharedPreferences.getInstance();
Completer<void> _populated = Completer();
factory AccountData() {
return _instance;
}
AccountData._construct() {
_updateFromStorage();
}
String? _username;
String? _password;
String getUsername() {
if(_username == null) throw Exception("Username not initialized");
return _username!;
}
String getPassword() {
if(_password == null) throw Exception("Password not initialized");
return _password!;
}
Future<void> setData(BuildContext context, String username, String password) async {
SharedPreferences storage = await _storage;
storage.setString(_usernameField, username);
storage.setString(_passwordField, password);
await _updateFromStorage();
}
void removeData(BuildContext context) async {
_populated = Completer();
Provider.of<AccountModel>(context, listen: false).setState(AccountModelState.loggedOut);
SharedPreferences storage = await _storage;
storage.remove(_usernameField);
storage.remove(_passwordField);
}
Future<void> _updateFromStorage() async {
SharedPreferences storage = await _storage;
await storage.reload();
if(storage.containsKey(_usernameField) && storage.containsKey(_passwordField)) {
_username = storage.getString(_usernameField);
_password = storage.getString(_passwordField);
_populated.complete();
}
}
Future<bool> waitForPopulation() async {
await _populated.future;
return isPopulated();
}
bool isPopulated() {
return _username != null && _password != null;
}
String buildHttpAuthString() {
if(!isPopulated()) throw Exception("AccountData (e.g. username or password) is not initialized!");
return "$_username:$_password";
}
}

View File

@ -1,17 +1,17 @@
import 'package:flutter/cupertino.dart';
class AccountModel extends ChangeNotifier {
bool _isLoggedIn = false;
AccountModelState _accountState = AccountModelState.undefined;
AccountModelState get state => _accountState;
bool get isLoggedIn => _isLoggedIn;
void logout() {
_isLoggedIn = false;
notifyListeners();
}
void login() {
_isLoggedIn = true;
void setState(AccountModelState state) {
_accountState = state;
notifyListeners();
}
}
enum AccountModelState {
undefined,
loggedIn,
loggedOut,
}

View File

@ -1,9 +1,9 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:marianum_mobile/storage/file/fileSettings.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../file/fileSettings.dart';
import '../gradeAverages/gradeAveragesSettings.dart';
import '../talk/talkSettings.dart';
import '../timetable/timetableSettings.dart';

View File

@ -1,15 +1,11 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_login/flutter_login.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../api/apiError.dart';
import '../../api/webuntis/queries/authenticate/authenticateParams.dart';
import '../../api/webuntis/queries/authenticate/authenticate.dart';
import '../../api/webuntis/webuntisError.dart';
import '../../model/accountData.dart';
import '../../model/accountModel.dart';
class Login extends StatefulWidget {
@ -27,8 +23,7 @@ class _LoginState extends State<Login> {
}
Future<String?> _login(LoginData data) async {
SharedPreferences preferences = await SharedPreferences.getInstance();
preferences.setBool("loggedIn", false);
AccountData().removeData(context);
try {
await Authenticate(
@ -36,23 +31,17 @@ class _LoginState extends State<Login> {
user: data.name,
password: data.password,
)
).run().then((value) => {
log(value.sessionId)
).run().then((value) async {
await AccountData().setData(context, data.name, data.password);
setState(() {
displayDisclaimerText = false;
});
});
} on WebuntisError catch(e) {
return e.toString();
} on ApiError catch(e) {
} catch(e) {
return e.toString();
}
setState(() {
displayDisclaimerText = false;
});
preferences.setBool("loggedIn", true);
preferences.setString("username", data.name);
preferences.setString("password", data.password);
return null;
}
@ -69,7 +58,7 @@ class _LoginState extends State<Login> {
userValidator: _checkInput,
passwordValidator: _checkInput,
onSubmitAnimationCompleted: () => Provider.of<AccountModel>(context, listen: false).login(),
onSubmitAnimationCompleted: () => Provider.of<AccountModel>(context, listen: false).setState(AccountModelState.loggedIn),
onLogin: _login,
onSignup: null,

View File

@ -4,8 +4,6 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:loader_overlay/loader_overlay.dart';
import 'package:marianum_mobile/storage/base/settingsProvider.dart';
import 'package:marianum_mobile/widget/loadingSpinner.dart';
import 'package:provider/provider.dart';
import '../../../api/marianumcloud/webdav/queries/listFiles/cacheableFile.dart';
@ -13,7 +11,9 @@ import '../../../api/marianumcloud/webdav/queries/listFiles/listFilesCache.dart'
import '../../../api/marianumcloud/webdav/queries/listFiles/listFilesResponse.dart';
import '../../../api/marianumcloud/webdav/webdavApi.dart';
import '../../../model/files/filesProps.dart';
import '../../../widget/errorView.dart';
import '../../../storage/base/settingsProvider.dart';
import '../../../widget/loadingSpinner.dart';
import '../../../widget/placeholderView.dart';
import '../../../widget/filePick.dart';
import 'fileUploadDialog.dart';
import 'fileElement.dart';
@ -224,7 +224,7 @@ class _FilesState extends State<Files> {
},
child: const Icon(Icons.add),
),
body: data == null ? const LoadingSpinner() : data!.files.isEmpty ? const ErrorView(icon: Icons.folder_off_rounded, text: "Der Ordner ist leer") : LoaderOverlay(
body: data == null ? const LoadingSpinner() : data!.files.isEmpty ? const PlaceholderView(icon: Icons.folder_off_rounded, text: "Der Ordner ist leer") : LoaderOverlay(
child: RefreshIndicator(
onRefresh: () {
_query();

View File

@ -1,10 +1,9 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:marianum_mobile/widget/loadingSpinner.dart';
import 'package:provider/provider.dart';
import '../../../../api/mhsl/message/getMessages/getMessagesResponse.dart';
import '../../../../model/message/messageProps.dart';
import '../../../../widget/loadingSpinner.dart';
import 'messageView.dart';

View File

@ -4,12 +4,12 @@ import 'package:flowder/flowder.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:jiffy/jiffy.dart';
import 'package:marianum_mobile/api/marianumcloud/talk/deleteMessage/deleteMessage.dart';
import 'package:marianum_mobile/model/chatList/chatProps.dart';
import 'package:provider/provider.dart';
import '../../../api/marianumcloud/talk/chat/getChatResponse.dart';
import '../../../api/marianumcloud/talk/deleteMessage/deleteMessage.dart';
import '../../../api/marianumcloud/talk/room/getRoomResponse.dart';
import '../../../model/chatList/chatProps.dart';
import '../../../theming/appTheme.dart';
import '../../../widget/debug/debugTile.dart';
import '../files/fileElement.dart';

View File

@ -1,14 +1,14 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:marianum_mobile/api/marianumcloud/talk/createRoom/createRoomParams.dart';
import 'package:marianum_mobile/widget/confirmDialog.dart';
import 'package:marianum_mobile/widget/loadingSpinner.dart';
import 'package:provider/provider.dart';
import '../../../api/marianumcloud/talk/createRoom/createRoom.dart';
import '../../../api/marianumcloud/talk/createRoom/createRoomParams.dart';
import '../../../model/chatList/chatListProps.dart';
import '../../../storage/base/settingsProvider.dart';
import '../../../widget/confirmDialog.dart';
import '../../../widget/loadingSpinner.dart';
import 'chatTile.dart';
import 'joinChat.dart';
import 'searchChat.dart';

View File

@ -2,13 +2,13 @@
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:loader_overlay/loader_overlay.dart';
import 'package:marianum_mobile/widget/loadingSpinner.dart';
import 'package:provider/provider.dart';
import '../../../api/marianumcloud/talk/chat/getChatResponse.dart';
import '../../../api/marianumcloud/talk/room/getRoomResponse.dart';
import '../../../theming/appTheme.dart';
import '../../../model/chatList/chatProps.dart';
import '../../../widget/loadingSpinner.dart';
import 'chatBubble.dart';
import 'chatTextfield.dart';

View File

@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
import '../../../api/marianumcloud/autocomplete/autocompleteApi.dart';
import '../../../api/marianumcloud/autocomplete/autocompleteResponse.dart';
import '../../../widget/errorView.dart';
import '../../../widget/placeholderView.dart';
class JoinChat extends SearchDelegate<String> {
CancelableOperation<AutocompleteResponse>? future;
@ -47,7 +47,7 @@ class JoinChat extends SearchDelegate<String> {
if(future != null) future!.cancel();
if(query.isEmpty) {
return const ErrorView(
return const PlaceholderView(
text: "Suche nach benutzern",
icon: Icons.person_search_outlined,
);
@ -81,7 +81,7 @@ class JoinChat extends SearchDelegate<String> {
);
} else if(snapshot.hasError) {
log(snapshot.error.toString());
return ErrorView(text: snapshot.error.toString());
return PlaceholderView(icon: Icons.search_off, text: snapshot.error.toString());
}
return const Center(child: CircularProgressIndicator());

View File

@ -2,7 +2,6 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:marianum_mobile/widget/loadingSpinner.dart';
import 'package:provider/provider.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
@ -11,7 +10,8 @@ import '../../../api/webuntis/queries/getRooms/getRoomsResponse.dart';
import '../../../api/webuntis/queries/getSubjects/getSubjectsResponse.dart';
import '../../../model/timetable/timetableProps.dart';
import '../../../storage/base/settingsProvider.dart';
import '../../../widget/errorView.dart';
import '../../../widget/loadingSpinner.dart';
import '../../../widget/placeholderView.dart';
import 'appointmenetComponent.dart';
import 'appointmentDetails.dart';
import 'timeRegionComponent.dart';
@ -68,7 +68,7 @@ class _TimetableState extends State<Timetable> {
GetHolidaysResponse holidays = value.getHolidaysResponse;
if(value.hasError) {
return ErrorView(
return PlaceholderView(
icon: Icons.calendar_month,
text: "Webuntis error: ${value.error.toString()}",
);

View File

@ -5,7 +5,7 @@ import 'package:package_info/package_info.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../model/accountModel.dart';
import '../../model/accountData.dart';
import '../../storage/base/settingsProvider.dart';
import '../../theming/appTheme.dart';
import '../../widget/confirmDialog.dart';
@ -52,9 +52,9 @@ class _SettingsState extends State<Settings> {
onConfirm: () {
SharedPreferences.getInstance().then((value) => {
value.clear(),
}).then((value) => {
Provider.of<AccountModel>(context, listen: false).logout(),
Navigator.popUntil(context, (route) => !Navigator.canPop(context)),
}).then((value) {
AccountData().removeData(context);
Navigator.popUntil(context, (route) => !Navigator.canPop(context));
});
},
),

View File

@ -7,7 +7,7 @@ import 'package:flutter/services.dart';
import 'package:jiffy/jiffy.dart';
import 'package:localstore/localstore.dart';
import '../../../widget/errorView.dart';
import '../../../widget/placeholderView.dart';
import 'jsonViewer.dart';
class CacheView extends StatefulWidget {
@ -93,7 +93,7 @@ class _CacheViewState extends State<CacheView> {
);
} else {
return const Center(
child: ErrorView(icon: Icons.hourglass_empty, text: "Keine Daten"),
child: PlaceholderView(icon: Icons.hourglass_empty, text: "Keine Daten"),
);
}
},

View File

@ -1,31 +0,0 @@
import 'package:flutter/material.dart';
class ErrorView extends StatelessWidget {
final IconData icon;
final String text;
const ErrorView({Key? key, this.icon = Icons.report_gmailerrorred, this.text = "Es ist ein Fehler aufgetreten!"}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Container(
margin: const EdgeInsets.only(top: 100, left: 20, right: 20),
child: Column(
children: [
Container(
margin: const EdgeInsets.all(30),
child: Icon(icon, color: Colors.grey, size: 60),
),
Text(text,
style: const TextStyle(
fontSize: 20,
color: Colors.grey,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
class PlaceholderView extends StatelessWidget {
final IconData icon;
final String text;
const PlaceholderView({Key? key, required this.icon, required this.text}) : super(key: key);
@override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: const TextStyle(),
child: Center(
child: Container(
margin: const EdgeInsets.only(top: 100, left: 20, right: 20),
child: Column(
children: [
Container(
margin: const EdgeInsets.all(30),
child: Icon(icon, color: Colors.grey, size: 60),
),
Text(text,
style: const TextStyle(
fontSize: 20,
color: Colors.grey,
),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
}