Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ public class FirebaseFunctionsPlugin: NSObject, FLTFirebasePluginProtocol, Flutt

let functions = Functions.functions(app: app, region: region ?? "")

#if DEBUG
functions.allowInsecureTokenAttachment =
arguments["allowInsecureTokenAttachment"] as? Bool ?? false
#endif

if let origin, !origin.isEmpty,
let url = URL(string: origin),
let host = url.host,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ class FunctionsStreamHandler: NSObject, FlutterStreamHandler {
let timeout = arguments["timeout"] as? Double
let limitedUseAppCheckToken = arguments["limitedUseAppCheckToken"] as? Bool ?? false

#if DEBUG
functions.allowInsecureTokenAttachment =
arguments["allowInsecureTokenAttachment"] as? Bool ?? false
#endif

if let origin,
let url = URL(string: origin),
let host = url.host,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,33 @@ class FirebaseFunctions extends FirebasePlugin {

String? _origin;

/// Whether Auth, FCM, and App Check tokens may be sent over plain HTTP to a
/// non-loopback host.
///
/// This mirrors `Functions.allowInsecureTokenAttachment` on the Firebase
/// Apple SDK. Set it before calling a function on a local emulator from a
/// physical device:
///
/// ```dart
/// FirebaseFunctions.instance.allowInsecureTokenAttachment = true;
/// FirebaseFunctions.instance.useFunctionsEmulator('192.168.1.10', 5001);
/// ```
///
/// Only Apple debug builds honor this value. `flutter run` is a debug build.
/// Profile and release builds compile the Apple SDK without the property, so
/// the value is ignored there. Android and web already attach these tokens,
/// so the value has no effect on those platforms, and setting it to `false`
/// does not withhold tokens on Android or web.
///
/// Sending tokens over plain HTTP exposes them on the local network. Enable
/// this only for a local emulator.
bool get allowInsecureTokenAttachment =>
delegate.allowInsecureTokenAttachment;

set allowInsecureTokenAttachment(bool value) {
delegate.allowInsecureTokenAttachment = value;
}

/// A reference to the Callable HTTPS trigger with the given name.
///
/// Should be the name of the Callable function in Firebase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,5 +175,32 @@ void main() {
expect(delegate.options.timeout.inSeconds, equals(1337));
});
});

group('.allowInsecureTokenAttachment', () {
test('defaults to false and writes through to the delegate', () {
final functions =
FirebaseFunctions.instanceFor(region: 'allow-insecure-token');
expect(functions.allowInsecureTokenAttachment, isFalse);
expect(functions.delegate.allowInsecureTokenAttachment, isFalse);

functions.allowInsecureTokenAttachment = true;

expect(functions.allowInsecureTokenAttachment, isTrue);
expect(functions.delegate.allowInsecureTokenAttachment, isTrue);
});

test('applies when set after the callable is created', () {
final functions = FirebaseFunctions.instanceFor(
region: 'allow-insecure-token-after-callable');
final callable = functions.httpsCallable('foo');

functions.allowInsecureTokenAttachment = true;

expect(
callable.delegate.functions.allowInsecureTokenAttachment,
isTrue,
);
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class MethodChannelHttpsCallable extends HttpsCallablePlatform {
'timeout': options.timeout.inMilliseconds,
'parameters': parameters,
'limitedUseAppCheckToken': options.limitedUseAppCheckToken,
'allowInsecureTokenAttachment': functions.allowInsecureTokenAttachment,
});

return _convertNested(result);
Expand Down Expand Up @@ -76,6 +77,7 @@ class MethodChannelHttpsCallable extends HttpsCallablePlatform {
'origin': origin,
'parameters': parameters,
'limitedUseAppCheckToken': options.limitedUseAppCheckToken,
'allowInsecureTokenAttachment': functions.allowInsecureTokenAttachment,
'timeout': options.timeout.inMilliseconds,
};
yield* channel.receiveBroadcastStream(eventData).map(_convertNested);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ abstract class FirebaseFunctionsPlatform extends PlatformInterface {
/// The region for the HTTPS trigger, such as "us-central1".
final String region;

/// Whether Auth, FCM, and App Check tokens may be sent over plain HTTP to a
/// non-loopback host.
///
/// Only Apple debug builds consult this value. Android and web ignore it.
bool allowInsecureTokenAttachment = false;

/// The current default [FirebaseFunctionsPlatform] instance.
///
/// It will always default to [MethodChannelFirebaseFunctions]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ void main() {
dynamic kParameters = {'foo': 'bar'};
HttpsCallableOptions kOptions = HttpsCallableOptions();
String kPlatformExceptionMessage = 'Mock platform exception thrown';
Map<String, Object?>? lastCallArguments;

group('$MethodChannelHttpsCallable', () {
setUpAll(() async {
FirebaseApp app = await Firebase.initializeApp();

TestCloudFunctionsHostApi.setUp(_TestCloudFunctionsHostApi(() async {
TestCloudFunctionsHostApi.setUp(
_TestCloudFunctionsHostApi((arguments) async {
lastCallArguments = arguments;
if (mockExceptionThrown) {
throw Exception();
} else if (mockPlatformExceptionThrown) {
Expand All @@ -56,6 +59,8 @@ void main() {
setUp(() async {
mockPlatformExceptionThrown = false;
mockExceptionThrown = false;
lastCallArguments = null;
functions!.allowInsecureTokenAttachment = false;
httpsCallable!.options = kOptions;
});

Expand Down Expand Up @@ -92,6 +97,17 @@ void main() {
});

group('call', () {
test('sends allowInsecureTokenAttachment, defaulting to false', () async {
await httpsCallable!.call();

expect(lastCallArguments?['allowInsecureTokenAttachment'], isFalse);

functions!.allowInsecureTokenAttachment = true;
await httpsCallable!.call();

expect(lastCallArguments?['allowInsecureTokenAttachment'], isTrue);
});

test('converts maps nested in lists', () async {
final originalParameters = kParameters;
addTearDown(() => kParameters = originalParameters);
Expand Down Expand Up @@ -120,16 +136,48 @@ void main() {
await testExceptionHandling('PLATFORM', httpsCallable!.call);
});
});

group('stream', () {
test('sends allowInsecureTokenAttachment on the event channel', () async {
functions!.allowInsecureTokenAttachment = true;
const channelName = 'plugins.flutter.io/firebase_functions/test_name_0';
Object? listenedArguments;
final messenger =
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
messenger.setMockMethodCallHandler(const MethodChannel(channelName),
(call) async {
if (call.method == 'listen') {
listenedArguments = call.arguments;
}
return null;
});
addTearDown(() {
messenger.setMockMethodCallHandler(
const MethodChannel(channelName), null);
});

final subscription = httpsCallable!.stream(null).listen((_) {});
await Future<void>.delayed(Duration.zero);
await subscription.cancel();

expect(listenedArguments, isA<Map>());
expect(
(listenedArguments! as Map)['allowInsecureTokenAttachment'],
isTrue,
);
});
});
});
}

class _TestCloudFunctionsHostApi implements TestCloudFunctionsHostApi {
_TestCloudFunctionsHostApi(this.callHandler);

final Future<Object?> Function() callHandler;
final Future<Object?> Function(Map<String, Object?> arguments) callHandler;

@override
Future<Object?> call(Map<String, Object?> arguments) => callHandler();
Future<Object?> call(Map<String, Object?> arguments) =>
callHandler(arguments);

@override
Future<void> registerEventChannel(Map<String, Object> arguments) async {}
Expand Down
Loading