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
2 changes: 2 additions & 0 deletions packages/firebase_admin_sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## 0.5.4-wip

- Fixed `Firestore` requests not carrying the SDK's usage-tracking headers
(`X-Firebase-Client`, `X-Goog-Api-Client`).
- 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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No, same values — just centralized into one shared constant instead of duplicated at each call site.

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
32 changes: 32 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,42 @@

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('.');

/// The Firebase Admin SDK's `X-Firebase-Client` identity.
const String _fireAdminFirebaseClientId = 'fire-admin-dart/$packageVersion';

/// The Firebase Admin SDK's tag within `X-Goog-Api-Client`.
const String _fireAdminApiClientTag = 'fire-admin/$packageVersion';

/// 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': _fireAdminFirebaseClientId,
'X-Goog-Api-Client': 'gl-dart/$dartVersion $_fireAdminApiClientTag',
});

/// 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': _fireAdminFirebaseClientId,
'X-Goog-Api-Client': _fireAdminApiClientTag,
};

/// 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