Skip to content

Commit 9d22534

Browse files
client: prevent PupilIdentityDto from having null sender property
1 parent c91692a commit 9d22534

2 files changed

Lines changed: 125 additions & 53 deletions

File tree

school_data_hub_flutter/lib/features/pupil/domain/pupil_identity_stream_suscription.dart

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ class PupilIdentityStream {
5252
.listen(
5353
(PupilIdentityDto event) async {
5454
final eventSender = event.sender;
55+
56+
// Defensive null check - validate sender before processing
57+
if (eventSender == null || eventSender.isEmpty) {
58+
_log.severe(
59+
'[${role.name.toUpperCase()}]: Received event with NULL/EMPTY sender! '
60+
'Type: ${event.type}, Value: ${event.value}. Skipping malformed event.',
61+
);
62+
// Skip processing this malformed event
63+
return;
64+
}
65+
5566
_log.info(
5667
'[${role.name.toUpperCase()}]: [${event.type}] Received event from $eventSender',
5768
);
@@ -94,6 +105,16 @@ class PupilIdentityStream {
94105
final targetUser = event.value.isNotEmpty
95106
? event.value
96107
: null;
108+
109+
// Validate user session before sending data
110+
111+
if (currentUser == null || currentUser.isEmpty) {
112+
_log.severe(
113+
'[${role.name.toUpperCase()}]: Cannot send data - username is null or empty',
114+
);
115+
break;
116+
}
117+
97118
if (targetUser == null) {
98119
// Legacy: no targeting, proceed for any receiver
99120
if (onRequestConfirmed != null) {
@@ -103,8 +124,7 @@ class PupilIdentityStream {
103124
await _client.pupilIdentity.sendPupilIdentityMessage(
104125
channelName,
105126
PupilIdentityDto(
106-
sender:
107-
di<HubSessionManager>().user!.userInfo!.userName!,
127+
sender: currentUser,
108128
dataTimeStamp:
109129
di<EnvManager>().activeEnv?.lastIdentitiesUpdate,
110130
type: 'data',
@@ -126,8 +146,7 @@ class PupilIdentityStream {
126146
await _client.pupilIdentity.sendPupilIdentityMessage(
127147
channelName,
128148
PupilIdentityDto(
129-
sender:
130-
di<HubSessionManager>().user!.userInfo!.userName!,
149+
sender: currentUser,
131150
type: 'data',
132151
dataTimeStamp:
133152
di<EnvManager>().activeEnv?.lastIdentitiesUpdate,
@@ -283,12 +302,21 @@ class PupilIdentityStream {
283302
lastIdentitiesUpdate: event.dataTimeStamp?.toUtc(),
284303
);
285304

305+
// Validate user session before sending confirmation
306+
final confirmUser =
307+
di<HubSessionManager>().user?.userInfo?.userName;
308+
if (confirmUser == null || confirmUser.isEmpty) {
309+
_log.severe(
310+
'[${role.name.toUpperCase()}]: Cannot send confirmation - username is null or empty',
311+
);
312+
break;
313+
}
314+
286315
// Send confirmation
287316
await _client.pupilIdentity.sendPupilIdentityMessage(
288317
channelName,
289318
PupilIdentityDto(
290-
sender:
291-
di<HubSessionManager>().user!.userInfo!.userName!,
319+
sender: confirmUser,
292320
type: 'ok',
293321
value: '',
294322
),

school_data_hub_flutter/lib/features/pupil/presentation/pupil_identity_stream_page/controllers/stream_controller.dart

Lines changed: 91 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ class PupilIdentityStreamController {
2222
final String? importedChannelName;
2323
late String channelName;
2424

25-
final String thisUserName = di<HubSessionManager>().user!.userInfo!.userName!;
26-
2725
final _notificationService = di<NotificationService>();
2826

2927
// Controller creates and owns the state
@@ -107,6 +105,18 @@ class PupilIdentityStreamController {
107105
}
108106
}
109107

108+
/// Validate user session before sending a message to prevent null sender
109+
String? _validateUserSession() {
110+
final currentUser = di<HubSessionManager>().user?.userInfo?.userName;
111+
if (currentUser == null || currentUser.isEmpty) {
112+
_log.severe(
113+
'Cannot send message: username is null or empty. User session may have expired.',
114+
);
115+
return null;
116+
}
117+
return currentUser;
118+
}
119+
110120
/// Reset sender state for new requests
111121
void resetSenderForNewRequest() {
112122
if (_isDisposed) {
@@ -147,11 +157,20 @@ class PupilIdentityStreamController {
147157
// Update local state to transmitting
148158
_handleRequestConfirmed();
149159

160+
// Validate user session before sending
161+
final validatedSender = _validateUserSession();
162+
if (validatedSender == null) {
163+
_log.severe(
164+
'Cannot confirm transfer for $userName: invalid user session',
165+
);
166+
return;
167+
}
168+
150169
// Notify receiver of confirmation (backward compatibility)
151170
await di<Client>().pupilIdentity.sendPupilIdentityMessage(
152171
channelName,
153172
PupilIdentityDto(
154-
sender: thisUserName,
173+
sender: validatedSender,
155174
type: 'confirmed',
156175
value: userName,
157176
),
@@ -161,7 +180,7 @@ class PupilIdentityStreamController {
161180
await di<Client>().pupilIdentity.sendPupilIdentityMessage(
162181
channelName,
163182
PupilIdentityDto(
164-
sender: thisUserName,
183+
sender: validatedSender,
165184
type: 'data',
166185
dataTimeStamp: di<EnvManager>().activeEnv?.lastIdentitiesUpdate,
167186
value: '$userName:${dataToSend ?? ''}',
@@ -207,6 +226,13 @@ class PupilIdentityStreamController {
207226
String userName, {
208227
bool isAutoRejection = false,
209228
}) async {
229+
// Validate user session before sending
230+
final validatedSender = _validateUserSession();
231+
if (validatedSender == null) {
232+
_log.severe('Cannot send rejection to $userName: invalid user session');
233+
return;
234+
}
235+
210236
final rejectionValue = isAutoRejection ? 'auto:$userName' : userName;
211237
int retryCount = 0;
212238
const maxRetries = 3;
@@ -220,7 +246,7 @@ class PupilIdentityStreamController {
220246
await di<Client>().pupilIdentity.sendPupilIdentityMessage(
221247
channelName,
222248
PupilIdentityDto(
223-
sender: thisUserName,
249+
sender: validatedSender,
224250
type: 'rejected',
225251
value: rejectionValue,
226252
),
@@ -314,14 +340,21 @@ class PupilIdentityStreamController {
314340
void _sendReceiverMessages() {
315341
_log.info('Receiver sending joined message and data request...');
316342

343+
// Validate user session before sending
344+
final validatedSender = _validateUserSession();
345+
if (validatedSender == null) {
346+
_log.severe('Cannot send receiver messages: invalid user session');
347+
return;
348+
}
349+
317350
// First send joined message
318351
di<Client>().pupilIdentity
319352
.sendPupilIdentityMessage(
320353
channelName,
321354
PupilIdentityDto(
322-
sender: thisUserName,
355+
sender: validatedSender,
323356
type: 'joined',
324-
value: thisUserName,
357+
value: validatedSender,
325358
),
326359
)
327360
.then((_) {
@@ -345,9 +378,9 @@ class PupilIdentityStreamController {
345378
return di<Client>().pupilIdentity.sendPupilIdentityMessage(
346379
channelName,
347380
PupilIdentityDto(
348-
sender: thisUserName,
381+
sender: validatedSender,
349382
type: 'request',
350-
value: thisUserName,
383+
value: validatedSender,
351384
),
352385
);
353386
})
@@ -557,17 +590,21 @@ class PupilIdentityStreamController {
557590
// If receiver is leaving, send close message before disposing
558591
if (role == PupilIdentityStreamRole.receiver &&
559592
state.streamState.isConnected.value) {
560-
// Send close message and ignore errors since we're disposing
561-
di<Client>().pupilIdentity
562-
.sendPupilIdentityMessage(
563-
channelName,
564-
PupilIdentityDto(
565-
sender: thisUserName,
566-
type: 'close',
567-
value: thisUserName,
568-
),
569-
)
570-
.ignore();
593+
// Validate user session before sending
594+
final validatedSender = _validateUserSession();
595+
if (validatedSender != null) {
596+
// Send close message and ignore errors since we're disposing
597+
di<Client>().pupilIdentity
598+
.sendPupilIdentityMessage(
599+
channelName,
600+
PupilIdentityDto(
601+
sender: validatedSender,
602+
type: 'close',
603+
value: validatedSender,
604+
),
605+
)
606+
.ignore();
607+
}
571608
}
572609

573610
_subscription?.cancel();
@@ -581,43 +618,50 @@ class PupilIdentityStreamController {
581618
}
582619

583620
void stopStream() async {
621+
// Validate user session before sending any messages
622+
final validatedSender = _validateUserSession();
623+
584624
// If sender is shutting down, notify all receivers
585625
if (role == PupilIdentityStreamRole.sender &&
586626
state.streamState.isConnected.value) {
587-
try {
588-
// Send shutdown message to all connected receivers
589-
await di<Client>().pupilIdentity.sendPupilIdentityMessage(
590-
channelName,
591-
PupilIdentityDto(
592-
sender: thisUserName,
593-
type: 'shutdown',
594-
value: 'Sender hat den Stream beendet',
595-
),
596-
);
597-
_log.info('Sent shutdown message to all receivers');
627+
if (validatedSender != null) {
628+
try {
629+
// Send shutdown message to all connected receivers
630+
await di<Client>().pupilIdentity.sendPupilIdentityMessage(
631+
channelName,
632+
PupilIdentityDto(
633+
sender: validatedSender,
634+
type: 'shutdown',
635+
value: 'Sender hat den Stream beendet',
636+
),
637+
);
638+
_log.info('Sent shutdown message to all receivers');
598639

599-
// Wait a brief moment to ensure message is sent
600-
await Future.delayed(const Duration(milliseconds: 200));
601-
} catch (e) {
602-
_log.warning('Failed to send shutdown message: $e');
640+
// Wait a brief moment to ensure message is sent
641+
await Future.delayed(const Duration(milliseconds: 200));
642+
} catch (e) {
643+
_log.warning('Failed to send shutdown message: $e');
644+
}
603645
}
604646
}
605647

606648
// If receiver is leaving, send close message to notify sender
607649
if (role == PupilIdentityStreamRole.receiver &&
608650
state.streamState.isConnected.value) {
609-
try {
610-
await di<Client>().pupilIdentity.sendPupilIdentityMessage(
611-
channelName,
612-
PupilIdentityDto(
613-
sender: thisUserName,
614-
type: 'close',
615-
value: thisUserName,
616-
),
617-
);
618-
_log.info('Sent close message to sender before leaving');
619-
} catch (e) {
620-
_log.warning('Failed to send close message: $e');
651+
if (validatedSender != null) {
652+
try {
653+
await di<Client>().pupilIdentity.sendPupilIdentityMessage(
654+
channelName,
655+
PupilIdentityDto(
656+
sender: validatedSender,
657+
type: 'close',
658+
value: validatedSender,
659+
),
660+
);
661+
_log.info('Sent close message to sender before leaving');
662+
} catch (e) {
663+
_log.warning('Failed to send close message: $e');
664+
}
621665
}
622666
}
623667

0 commit comments

Comments
 (0)