File: preview_server_proxy.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (454 lines) | stat: -rw-r--r-- 17,183 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/data_sharing/internal/preview_server_proxy.h"

#include <algorithm>
#include <optional>
#include <utility>

#include "base/base64.h"
#include "base/base64url.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/json/values_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/version_info/channel.h"
#include "components/data_sharing/public/features.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/sync/base/command_line_switches.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/unique_position.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync/protocol/shared_tab_group_data_specifics.pb.h"
#include "google_apis/common/base_requests.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"

using endpoint_fetcher::EndpointFetcher;
using endpoint_fetcher::EndpointResponse;

namespace data_sharing {
namespace {
// RPC timeout duration.
constexpr base::TimeDelta kTimeout = base::Milliseconds(5000);

// Content type for network request.
constexpr char kContentType[] = "application/json; charset=UTF-8";
// OAuth name.
constexpr char kOAuthName[] = "shared_data_preview";
// OAuth scope of the server.
constexpr char kOAuthScope[] = "https://www.googleapis.com/auth/chromesync";

// Server addresses to get preview data.
constexpr char kDefaultServiceBaseUrl[] =
    "https://staging-chromesyncsharedentities-pa.googleapis.com/v1";
constexpr char kAutopushServiceBaseUrl[] =
    "https://autopush-chromesyncsharedentities-pa.sandbox.googleapis.com/v1";
constexpr char kStableAndBetaServiceBaseUrl[] =
    "https://chromesyncsharedentities-pa.googleapis.com/v1";

// How many share entities to retrieve for preview.
constexpr int kDefaultPreviewDataSize = 550;
constexpr base::FeatureParam<int> kPreviewDataSize{
    &features::kDataSharingFeature, "preview_data_size",
    kDefaultPreviewDataSize};

// lowerCamelCase JSON proto message keys.
constexpr char kSharedEntitiesKey[] = "sharedEntities";
constexpr char kDeletedKey[] = "deleted";
constexpr char kCollaborationKey[] = "collaboration";
constexpr char kCollaborationIdKey[] = "collaborationId";
constexpr char kSpecificsKey[] = "specifics";
constexpr char kSharedGroupDataKey[] = "sharedTabGroupData";
constexpr char kGuidKey[] = "guid";
constexpr char kUpdateTimeWindowsEpochMicrosKey[] =
    "updateTimeWindowsEpochMicros";
constexpr char kTabKey[] = "tab";
constexpr char kTabGroupKey[] = "tabGroup";
constexpr char kUrlKey[] = "url";
constexpr char kTitleKey[] = "title";
constexpr char kSharedTabGroupGuidKey[] = "sharedTabGroupGuid";
constexpr char kUniquePositionKey[] = "uniquePosition";
constexpr char kCustomCompressedV1Key[] = "customCompressedV1";
constexpr char kColorKey[] = "color";
constexpr char kGroupVialoationError[] = "SAME_CUSTOMER_DASHER_POLICY_VIOLATED";

struct TabData {
  std::string url;
  syncer::UniquePosition position;

  TabData(const std::string& url, const syncer::UniquePosition& position)
      : url(url), position(position) {}

  bool operator<(const TabData& other) const {
    return position.LessThan(other.position);
  }
};
// Network annotation for getting preview data from server.
constexpr net::NetworkTrafficAnnotationTag
    kGetSharedDataPreviewTrafficAnnotation =
        net::DefineNetworkTrafficAnnotation("chrome_data_sharing_preview",
                                            R"(
          semantics {
            sender: "Chrome Data Sharing"
            description:
              "Ask server for a preview of the data shared to a group."
            trigger:
              "A Chrome-initiated request that requires user enabling the "
              "data sharing feature. The request is sent after user receives "
              "an invitation link to join a group, and click on a button to "
              "get a preview of the data shared to that group."
            user_data {
              type: OTHER
              type: ACCESS_TOKEN
            }
            data:
              "Group ID and access token obtained from the invitation that "
              "the user has received."
            destination: GOOGLE_OWNED_SERVICE
            internal {
              contacts { email: "chrome-data-sharing-eng@google.com" }
            }
            last_reviewed: "2024-08-20"
          }
          policy {
            cookies_allowed: NO
            setting:
              "This fetch is enabled for any non-enterprise user that has "
              "the data sharing feature enabled and is signed in."
            chrome_policy {}
          }
        )");

// Find a a value for a field from a child dictionary in json.
std::optional<std::string> GetFieldValueFromChildDict(
    const base::Value::Dict& parent_dict,
    const std::string& child_dict_name,
    const std::string& field_name) {
  auto* child_dict = parent_dict.FindDict(child_dict_name);
  if (!child_dict) {
    return std::nullopt;
  }
  auto* field_value = child_dict->FindString(field_name);
  if (!field_value) {
    return std::nullopt;
  }
  return *field_value;
}

// Parse the shared tab from the dict.
std::optional<sync_pb::SharedTab> ParseSharedTab(
    const base::Value::Dict& dict) {
  auto* url = dict.FindString(kUrlKey);
  if (!url) {
    return std::nullopt;
  }

  auto* title = dict.FindString(kTitleKey);

  auto* shared_tab_group_guid = dict.FindString(kSharedTabGroupGuidKey);
  if (!shared_tab_group_guid) {
    return std::nullopt;
  }

  auto custom_compressed = GetFieldValueFromChildDict(dict, kUniquePositionKey,
                                                      kCustomCompressedV1Key);

  std::optional<sync_pb::SharedTab> shared_tab =
      std::make_optional<sync_pb::SharedTab>();
  shared_tab->set_url(*url);
  if (title) {
    shared_tab->set_title(*title);
  }
  shared_tab->set_shared_tab_group_guid(*shared_tab_group_guid);
  if (custom_compressed) {
    std::string decoded;
    base::Base64Decode(custom_compressed.value(), &decoded);
    shared_tab->mutable_unique_position()->set_custom_compressed_v1(decoded);
  }
  return shared_tab;
}

// Parse the entity specifics from the dict.
std::optional<sync_pb::EntitySpecifics> ParseEntitySpecifics(
    const base::Value::Dict& dict) {
  auto* shared_tab_group_dict = dict.FindDict(kSharedGroupDataKey);
  if (!shared_tab_group_dict) {
    return std::nullopt;
  }

  std::optional<sync_pb::EntitySpecifics> specifics =
      std::make_optional<sync_pb::EntitySpecifics>();
  sync_pb::SharedTabGroupDataSpecifics* tab_group_data =
      specifics->mutable_shared_tab_group_data();
  auto* guid = shared_tab_group_dict->FindString(kGuidKey);
  if (!guid) {
    return std::nullopt;
  }
  tab_group_data->set_guid(*guid);

  auto* update_time_str =
      shared_tab_group_dict->FindString(kUpdateTimeWindowsEpochMicrosKey);
  uint64_t update_time;
  if (update_time_str && base::StringToUint64(*update_time_str, &update_time)) {
    tab_group_data->set_update_time_windows_epoch_micros(update_time);
  }

  auto* tab_dict = shared_tab_group_dict->FindDict(kTabKey);
  if (tab_dict) {
    auto shared_tab = ParseSharedTab(*tab_dict);
    if (shared_tab) {
      *(tab_group_data->mutable_tab()) = std::move(shared_tab.value());
    } else {
      return std::nullopt;
    }
  } else {
    auto* tab_group_dict = shared_tab_group_dict->FindDict(kTabGroupKey);
    if (tab_group_dict) {
      auto* title = tab_group_dict->FindString(kTitleKey);
      if (!title) {
        return std::nullopt;
      }
      sync_pb::SharedTabGroup* shared_tab_group =
          tab_group_data->mutable_tab_group();
      shared_tab_group->set_title(*title);
      auto* color = tab_group_dict->FindString(kColorKey);
      if (color) {
        sync_pb::SharedTabGroup::Color group_color;
        SharedTabGroup_Color_Parse(*color, &group_color);
        shared_tab_group->set_color(group_color);
      }
    } else {
      return std::nullopt;
    }
  }

  return specifics;
}

// Deserializes a EntitySpecifics from JSON.
std::optional<sync_pb::EntitySpecifics> Deserialize(const base::Value& value) {
  if (!value.is_dict()) {
    return std::nullopt;
  }

  const base::Value::Dict& value_dict = value.GetDict();
  // Check if entry is deleted.
  auto deleted = value_dict.FindBool(kDeletedKey);
  if (deleted.has_value() && deleted.value()) {
    return std::nullopt;
  }

  // Get group id.
  auto collaboration_id = GetFieldValueFromChildDict(
      value_dict, kCollaborationKey, kCollaborationIdKey);
  if (!collaboration_id) {
    return std::nullopt;
  }

  // Get entity specifics.
  auto* specifics_dict = value_dict.FindDict(kSpecificsKey);
  if (!specifics_dict) {
    return std::nullopt;
  }

  return ParseEntitySpecifics(*specifics_dict);
}

}  // namespace

PreviewServerProxy::PreviewServerProxy(
    signin::IdentityManager* identity_manager,
    const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
    version_info::Channel channel)
    : identity_manager_(identity_manager),
      url_loader_factory_(url_loader_factory),
      channel_(channel) {}

PreviewServerProxy::~PreviewServerProxy() = default;

void PreviewServerProxy::GetSharedDataPreview(
    const GroupToken& group_token,
    std::optional<syncer::DataType> data_type,
    base::OnceCallback<
        void(const DataSharingService::SharedDataPreviewOrFailureOutcome&)>
        callback) {
  if (!group_token.IsValid()) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(
            std::move(callback),
            base::unexpected(
                DataSharingService::DataPreviewActionFailure::kOtherFailure)));
    return;
  }
  std::string data_type_str;
  if (data_type.has_value()) {
    int field_number = GetSpecificsFieldNumberFromDataType(*data_type);
    data_type_str = base::NumberToString(field_number);
  } else {
    data_type_str = "-";
  }

  // Path in the URL to get shared entnties preview, {collaborationId} needs to
  // be replaced by the caller.
  const char kSharedEntitiesPreviewPath[] =
      "collaborations/{collaborationId}/dataTypes/{data_type}/"
      "sharedEntities:preview";
  std::string shared_entities_preview_path = kSharedEntitiesPreviewPath;
  std::string encoded_id;
  base::Base64UrlEncode(group_token.group_id.value(),
                        base::Base64UrlEncodePolicy::OMIT_PADDING, &encoded_id);
  base::ReplaceFirstSubstringAfterOffset(&shared_entities_preview_path, 0,
                                         "{collaborationId}", encoded_id);
  base::ReplaceFirstSubstringAfterOffset(&shared_entities_preview_path, 0,
                                         "{data_type}", data_type_str);
  std::string url_str = GetPreviewServerURLString();
  url_str.append("/").append(shared_entities_preview_path);
  GURL url = GURL(url_str);

  // Query string in the URL to get shared entnties preview. {token} needs to
  // be replaced by the caller. {pageSize} can be configured through finch.
  const std::string kQueryString =
      "accessToken={token}&pageToken=&pageSize={pageSize}";
  std::string query_str = kQueryString;
  base::ReplaceFirstSubstringAfterOffset(&query_str, 0, "{token}",
                                         group_token.access_token);
  base::ReplaceFirstSubstringAfterOffset(
      &query_str, 0, "{pageSize}",
      base::NumberToString(kPreviewDataSize.Get()));
  GURL::Replacements replacements;
  replacements.SetQueryStr(query_str);
  url = url.ReplaceComponents(replacements);
  auto fetcher = CreateEndpointFetcher(url);
  auto* const fetcher_ptr = fetcher.get();

  fetcher_ptr->Fetch(base::BindOnce(&PreviewServerProxy::HandleServerResponse,
                                    weak_ptr_factory_.GetWeakPtr(),
                                    std::move(callback), std::move(fetcher)));
}

std::unique_ptr<EndpointFetcher> PreviewServerProxy::CreateEndpointFetcher(
    const GURL& url) {
  return std::make_unique<EndpointFetcher>(
      url_loader_factory_, kOAuthName, url, net::HttpRequestHeaders::kGetMethod,
      kContentType, std::vector<std::string>{kOAuthScope}, kTimeout,
      /* post_data= */ std::string(), kGetSharedDataPreviewTrafficAnnotation,
      identity_manager_, signin::ConsentLevel::kSignin);
}

void PreviewServerProxy::HandleServerResponse(
    base::OnceCallback<void(
        const DataSharingService::SharedDataPreviewOrFailureOutcome&)> callback,
    std::unique_ptr<EndpointFetcher> endpoint_fetcher,
    std::unique_ptr<EndpointResponse> response) {
  if (response->http_status_code != net::HTTP_OK || response->error_type) {
    DLOG(ERROR) << "Got bad response (" << response->http_status_code
                << ") for shared data preview!";
    DataSharingService::DataPreviewActionFailure failure =
        DataSharingService::DataPreviewActionFailure::kOtherFailure;
    if (response->http_status_code == net::HTTP_CONFLICT) {
      failure = DataSharingService::DataPreviewActionFailure::kGroupFull;
    } else if (response->http_status_code == net::HTTP_FORBIDDEN) {
      failure = DataSharingService::DataPreviewActionFailure::kPermissionDenied;
      std::optional<std::string> reason =
          google_apis::MapJsonErrorToReason(response->response);
      if (reason.has_value() && reason.value() == kGroupVialoationError) {
        failure = DataSharingService::DataPreviewActionFailure::
            kGroupClosedByOrganizationPolicy;
      }
    }
    std::move(callback).Run(base::unexpected(failure));
    return;
  }

  data_decoder::DataDecoder::ParseJsonIsolated(
      response->response,
      base::BindOnce(&PreviewServerProxy::OnResponseJsonParsed,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void PreviewServerProxy::OnResponseJsonParsed(
    base::OnceCallback<void(
        const DataSharingService::SharedDataPreviewOrFailureOutcome&)> callback,
    data_decoder::DataDecoder::ValueOrError result) {
  SharedDataPreview preview;
  if (result.has_value() && result->is_dict()) {
    if (auto* response_json = result->GetDict().FindList(kSharedEntitiesKey)) {
      std::optional<SharedTabGroupPreview> group_preview;
      std::vector<TabData> tab_data;
      for (const auto& shared_entity_json : *response_json) {
        if (auto specifics = Deserialize(shared_entity_json)) {
          if (specifics && specifics->has_shared_tab_group_data()) {
            const sync_pb::SharedTabGroupDataSpecifics& tab_group_data =
                specifics->shared_tab_group_data();
            if (tab_group_data.has_tab_group()) {
              group_preview = SharedTabGroupPreview();
              group_preview->title = tab_group_data.tab_group().title();
            } else if (tab_group_data.has_tab()) {
              tab_data.emplace_back(
                  tab_group_data.tab().url(),
                  syncer::UniquePosition::FromProto(
                      tab_group_data.tab().unique_position()));
            }
          }
        }
      }
      if (group_preview && !tab_data.empty()) {
        // Sort all the tabs.
        std::sort(tab_data.begin(), tab_data.end());
        for (const auto& data : tab_data) {
          group_preview->tabs.emplace_back(GURL(data.url));
        }
        preview.shared_tab_group_preview = std::move(group_preview);
      }
    }
  }
  if (!preview.shared_tab_group_preview) {
    std::move(callback).Run(base::unexpected(
        DataSharingService::DataPreviewActionFailure::kOtherFailure));
  } else {
    std::move(callback).Run(std::move(preview));
  }
}

std::string PreviewServerProxy::GetPreviewServerURLString() const {
  // If there is a param set in a field trial, use the value from that as a
  // failsafe.
  std::string field_trial_param = GetFieldTrialParamValueByFeature(
      features::kDataSharingFeature, "preview_service_base_url");
  if (!field_trial_param.empty()) {
    return field_trial_param;
  }

  // If the sync server is set manually (e.g. with
  // chrome://flags/#use-sync-sandbox), use autopush.
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          syncer::kSyncServiceURL)) {
    return kAutopushServiceBaseUrl;
  }

  // Stable and Beta channel should use the production server.
  if (GetChannel() == version_info::Channel::STABLE ||
      GetChannel() == version_info::Channel::BETA) {
    return kStableAndBetaServiceBaseUrl;
  }

  // Other channels and local builds use the default service URL.
  return kDefaultServiceBaseUrl;
}

version_info::Channel PreviewServerProxy::GetChannel() const {
  return channel_;
}

}  // namespace data_sharing