Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions integration_test/add_tokens_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ void main() {
(tester) async {
await tester.pumpWidget(TestsAppWrapper(
overrides: [
settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)),
tokenProvider.overrideWith((ref) => TokenNotifier(repository: mockTokenRepository)),
settingsProvider.overrideWith(() => SettingsNotifier(repository: mockSettingsRepository)),
tokenProvider.overrideWith(() => TokenNotifier(repository: mockTokenRepository)),
],
child: const EduMFAAuthenticator(),
));
Expand Down
4 changes: 2 additions & 2 deletions integration_test/views_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ void main() {
testWidgets('Views Test', (tester) async {
await tester.pumpWidget(TestsAppWrapper(
overrides: [
settingsProvider.overrideWith((ref) => SettingsNotifier(repository: mockSettingsRepository)),
tokenProvider.overrideWith((ref) => TokenNotifier(
settingsProvider.overrideWith(() => SettingsNotifier(repository: mockSettingsRepository)),
tokenProvider.overrideWith(() => TokenNotifier(
repository: mockTokenRepository,
rsaUtils: mockRsaUtils,
firebaseUtils: mockFirebaseUtils,
Expand Down
2 changes: 1 addition & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class EduMFAAuthenticator extends ConsumerWidget {

return LayoutBuilder(builder: (context, constraints) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(appConstraintsProvider.notifier).state = constraints;
ref.read(appConstraintsProvider.notifier).setConstraints(constraints);
});
return DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
Expand Down
27 changes: 14 additions & 13 deletions lib/state_notifiers/deeplink_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,22 @@ import 'package:flutter_riverpod/legacy.dart';

bool _initialUriIsHandled = false;

class DeeplinkNotifier extends StateNotifier<DeepLink?> {
class DeeplinkNotifier extends Notifier<DeepLink?> {
final List<StreamSubscription> _subs = [];
final List<DeeplinkSource> _sources;
DeeplinkNotifier({required this._sources})
: super(null) {
_handleInitialUri();
_handleIncomingLinks();
}
DeeplinkNotifier({required List<DeeplinkSource> sources})
: _sources = sources;

@override
void dispose() {
for (var sub in _subs) {
sub.cancel();
}
super.dispose();
DeepLink? build() {
ref.onDispose(() {
for (var sub in _subs) {
sub.cancel();
}
});
_handleInitialUri();
_handleIncomingLinks();
return null;
}

/// Handle incoming links - the ones that the app will recieve from the OS
Expand All @@ -33,7 +34,7 @@ class DeeplinkNotifier extends StateNotifier<DeepLink?> {
for (var source in _sources) {
_subs.add(source.stream.listen((Uri? uri) {
Logger.info('Got uri from ${source.name}');
if (!mounted) return;
if (!ref.mounted) return;
if (uri == null) return;
state = DeepLink(uri);
}, onError: (Object err) {
Expand All @@ -50,7 +51,7 @@ class DeeplinkNotifier extends StateNotifier<DeepLink?> {
for (var source in _sources) {
final initialUri = await source.initialUri;
if (initialUri != null) {
if (!mounted) return;
if (!ref.mounted) return;
state = DeepLink(initialUri, fromInit: true);
Logger.info('Got initial uri from ${source.name}');
return; // There can only be one initial uri
Expand Down
92 changes: 76 additions & 16 deletions lib/state_notifiers/push_request_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,44 +23,85 @@

import 'dart:async';

import 'package:flutter_riverpod/legacy.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart';
import 'package:edumfa_authenticator/model/push_request.dart';
import 'package:edumfa_authenticator/model/tokens/push_token.dart';
import 'package:edumfa_authenticator/utils/firebase_utils.dart';
import 'package:edumfa_authenticator/utils/logger.dart';
import 'package:edumfa_authenticator/utils/network_utils.dart';
import 'package:edumfa_authenticator/utils/push_provider.dart';
import 'package:edumfa_authenticator/utils/riverpod_providers.dart';
import 'package:edumfa_authenticator/utils/rsa_utils.dart';

/// Interface between the [PushProvider] and the UI.
class PushRequestNotifier extends StateNotifier<PushRequest?> {
class PushRequestNotifier extends Notifier<PushRequest?> {
// Used for periodically polling for push challenges

final PushProvider _pushProvider;
final EduMFAIOClient _ioClient;
final RsaUtils _rsaUtils;
final FirebaseUtils _firebaseUtils;
final PushRequest? _initState;
final bool _attachProviderListeners;

PushRequestNotifier({
PushRequest? initState,
this._initState,
PushProvider? pushProvider,
EduMFAIOClient? ioClient,
RsaUtils? rsaUtils,
FirebaseUtils? firebaseUtils,
}) : _ioClient = ioClient ?? const EduMFAIOClient(),
_pushProvider = pushProvider ?? PushProvider(),
_rsaUtils = rsaUtils ?? const RsaUtils(),
super(initState) {
_pushProvider.initialize(pushSubscriber: this, firebaseUtils: firebaseUtils ?? FirebaseUtils());
this._attachProviderListeners = false,
}) : _ioClient = ioClient ?? const EduMFAIOClient(),
_pushProvider = pushProvider ?? PushProvider(),
_rsaUtils = rsaUtils ?? const RsaUtils(),
_firebaseUtils = firebaseUtils ?? FirebaseUtils();

@override
PushRequest? build() {
Logger.info("New PushRequestNotifier created", name: 'pushRequestProvider');
if (_attachProviderListeners) {
ref.listen(settingsProvider, (previous, next) {
if (previous?.enablePolling != next.enablePolling) {
Logger.info(
"Polling enabled changed from ${previous?.enablePolling} to ${next.enablePolling}",
name: 'pushRequestProvider#settingsProvider',
);
_pushProvider.setPollingEnabled(next.enablePolling);
}
});
ref.listen(appStateProvider, (previous, next) {
if (previous == AppLifecycleState.paused &&
next == AppLifecycleState.resumed) {
Logger.info(
'Polling for challenges on resume',
name: 'pushRequestProvider#appStateProvider',
);
_pushProvider.pollForChallenges(isManually: false);
}
});
}
_pushProvider.initialize(
pushSubscriber: this,
firebaseUtils: _firebaseUtils,
);
return _initState;
}

// ACTIONS
Future<bool> acceptPop(PushToken pushToken) async {
final pushRequest = pushToken.pushRequests.tryPop();
if (pushRequest == null) return false;
Logger.info('Approving push request.', name: 'push_request_notifier.dart#approve');
Logger.info(
'Approving push request.',
name: 'push_request_notifier.dart#approve',
);
final updatedPushRequest = pushRequest.copyWith(accepted: true);
final successfullyApproved = await _handleReaction(pushRequest: updatedPushRequest, token: pushToken);
final successfullyApproved = await _handleReaction(
pushRequest: updatedPushRequest,
token: pushToken,
);
if (!successfullyApproved) {
pushToken.pushRequests.add(pushRequest);
return false;
Expand All @@ -72,9 +113,15 @@ class PushRequestNotifier extends StateNotifier<PushRequest?> {
Future<bool> declinePop(PushToken pushToken) async {
final pushRequest = pushToken.pushRequests.tryPop();
if (pushRequest == null) return false;
Logger.info('Decline push request.', name: 'push_request_notifier.dart#decline');
Logger.info(
'Decline push request.',
name: 'push_request_notifier.dart#decline',
);
final updatedPushRequest = pushRequest.copyWith(accepted: false);
final successfullyDeclined = await _handleReaction(pushRequest: updatedPushRequest, token: pushToken);
final successfullyDeclined = await _handleReaction(
pushRequest: updatedPushRequest,
token: pushToken,
);
if (!successfullyDeclined) {
pushToken.pushRequests.add(pushRequest);
return false;
Expand All @@ -85,10 +132,16 @@ class PushRequestNotifier extends StateNotifier<PushRequest?> {

void newRequest(PushRequest pushRequest) => state = pushRequest;

Future<bool> _handleReaction({required PushRequest pushRequest, required PushToken token}) async {
Future<bool> _handleReaction({
required PushRequest pushRequest,
required PushToken token,
}) async {
if (pushRequest.accepted == null) return false;

Logger.info('Push auth request accepted=${pushRequest.accepted}, sending response to edumfa', name: 'token_widgets.dart#handleReaction');
Logger.info(
'Push auth request accepted=${pushRequest.accepted}, sending response to edumfa',
name: 'token_widgets.dart#handleReaction',
);

// signature ::= {nonce}|{serial}[|decline]
String msg = '${pushRequest.nonce}|${token.serial}';
Expand All @@ -114,9 +167,16 @@ class PushRequestNotifier extends StateNotifier<PushRequest?> {
body["decline"] = "1";
}

Response response = await _ioClient.doPost(sslVerify: pushRequest.sslVerify, url: pushRequest.uri, body: body);
Response response = await _ioClient.doPost(
sslVerify: pushRequest.sslVerify,
url: pushRequest.uri,
body: body,
);
if (response.statusCode != 200) {
Logger.warning('Sending push request response failed.', name: 'token_widgets.dart#handleReaction');
Logger.warning(
'Sending push request response failed.',
name: 'token_widgets.dart#handleReaction',
);
return false;
}

Expand Down
9 changes: 7 additions & 2 deletions lib/state_notifiers/settings_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ import 'package:edumfa_authenticator/utils/push_provider.dart';
/// This class provies access to the device specific settings.
/// It also ensures that the settings are saved to the device.
/// To Update a state use: ref.read(settingsProvider.notifier).anyMethod(value)
class SettingsNotifier extends StateNotifier<SettingsState> {
class SettingsNotifier extends Notifier<SettingsState> {
late Future<SettingsState> loadingRepo;
final SettingsRepository _repo;
final SettingsState? _initialState;

SettingsNotifier({
required SettingsRepository repository,
SettingsState? initialState,
}) : _repo = repository,
super(initialState ?? SettingsState()) {
_initialState = initialState;

@override
SettingsState build() {
loadFromRepo();
return _initialState ?? SettingsState();
}
void loadFromRepo() async {
loadingRepo = Future(() async {
Expand Down
Loading
Loading