Skip to content
1 change: 1 addition & 0 deletions packages/firebase_admin_sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## 0.5.4-wip

- Fixed `Firestore` requests not carrying the SDK's usage-tracking headers (`X-Firebase-Client`, `X-Goog-Api-Client`).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this meant to be wrapped at 80 columns.

- Update dependency `googleapis_auth: ^2.3.3` to fix `auth/insufficient-permission`
errors with Application Default Credentials that have no quota project set.

Expand Down
1 change: 0 additions & 1 deletion packages/firebase_admin_sdk/lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import '../messaging.dart';
import '../security_rules.dart';
import '../storage.dart';
import 'utils/utils.dart';
import 'version.g.dart';

part 'app/app_exception.dart';
part 'app/app_options.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ class FirebaseUserAgentClient extends BaseClient

@override
Future<StreamedResponse> send(BaseRequest request) {
request.headers['X-Firebase-Client'] = 'fire-admin-dart/$packageVersion';
request.headers['X-Goog-Api-Client'] =
'gl-dart/$dartVersion fire-admin/$packageVersion';
request.headers.addAll(firebaseUserAgentHeaders);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this semantically different than what was there before?

return _client.send(request);
}

Expand Down
5 changes: 5 additions & 0 deletions packages/firebase_admin_sdk/lib/src/firestore/firestore.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import 'package:google_cloud_firestore/google_cloud_firestore.dart'
import 'package:meta/meta.dart';

import '../app.dart';
import '../utils/utils.dart';

/// Default database ID used by Firestore
const String kDefaultDatabaseId = '(default)';
Expand Down Expand Up @@ -152,6 +153,10 @@ class Firestore implements FirebaseService {
settings = settings.copyWith(projectId: projectId);
}

settings = settings.copyWith(
headers: {...firestoreUsageTrackingHeaders, ...?settings.headers},
);

return settings;
}

Expand Down
26 changes: 26 additions & 0 deletions packages/firebase_admin_sdk/lib/src/utils/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,36 @@

import 'dart:io';

import '../version.g.dart';

/// The current Dart SDK version in semver format (e.g. "3.3.0").
String get dartVersion =>
Platform.version.split(RegExp('[^0-9]')).take(3).join('.');

/// Headers that identify a request as originating from this SDK, for
/// Firebase backend usage tracking.
///
/// Used by `FirebaseUserAgentClient`, which wraps HTTP clients (Auth,
/// Messaging, and other services that go through `FirebaseApp.client`) that
/// don't already set a client-identification header of their own.
final Map<String, String> firebaseUserAgentHeaders = Map.unmodifiable({
'X-Firebase-Client': 'fire-admin-dart/$packageVersion',
'X-Goog-Api-Client': 'gl-dart/$dartVersion fire-admin/$packageVersion',
});

/// Headers to attach to Firestore requests to identify them as originating
/// from this SDK, for Firebase backend usage tracking.
///
/// Unlike [firebaseUserAgentHeaders], `X-Goog-Api-Client` here omits the
/// `gl-dart/{version}` runtime tag: `google_cloud_firestore`'s
/// `FirestoreRequestClient` appends this value onto one that
/// `package:google_cloud_rpc` already set (which already carries that tag),
/// so repeating it here would duplicate it.
const Map<String, String> firestoreUsageTrackingHeaders = {
'X-Firebase-Client': 'fire-admin-dart/$packageVersion',
'X-Goog-Api-Client': 'fire-admin/$packageVersion',
};

/// Generates the update mask for the provided object.
/// Note this will ignore the last key with value `null`.
List<String> _generateUpdateMask(Object? obj, String root) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'dart:io';

import 'package:firebase_admin_sdk/firebase_admin_sdk.dart';
import 'package:firebase_admin_sdk/src/firestore/firestore.dart';
import 'package:firebase_admin_sdk/src/utils/utils.dart';
import 'package:google_cloud_firestore/google_cloud_firestore.dart' as gfs;
import 'package:googleapis_auth/auth_io.dart' as auth;
import 'package:mocktail/mocktail.dart';
Expand Down Expand Up @@ -254,6 +255,39 @@ void main() {
});
});

group('usage-tracking headers', () {
test('are attached when no user headers are provided', () {
final settings = firestoreService.buildSettingsForTesting('db', null);

expect(settings.headers, firestoreUsageTrackingHeaders);
});

test('are merged with, not replaced by, user-provided headers', () {
final settings = firestoreService.buildSettingsForTesting(
'db',
const gfs.Settings(headers: {'X-Trace-Id': 'abc123'}),
);

expect(settings.headers, {
...firestoreUsageTrackingHeaders,
'X-Trace-Id': 'abc123',
});
});

test('can be overridden by a user-provided header of the same name', () {
final settings = firestoreService.buildSettingsForTesting(
'db',
const gfs.Settings(headers: {'X-Firebase-Client': 'custom-value'}),
);

expect(settings.headers!['X-Firebase-Client'], 'custom-value');
expect(
settings.headers!['X-Goog-Api-Client'],
firestoreUsageTrackingHeaders['X-Goog-Api-Client'],
);
});
});

group('lifecycle', () {
test('should terminate all databases on delete', () async {
final db1 = firestoreService.getDatabase('lifecycle-1');
Expand Down
Loading