summaryrefslogtreecommitdiffstats
path: root/extensions/permissions/PermissionDelegateHandler.cpp
blob: b81943c9b929ca9103082680b3ced4656c7384f3 (plain)
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
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/PermissionDelegateHandler.h"

#include "nsGlobalWindowInner.h"
#include "nsPIDOMWindow.h"
#include "nsIPrincipal.h"
#include "nsContentPermissionHelper.h"

#include "mozilla/BasePrincipal.h"
#include "mozilla/StaticPrefs_permissions.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/FeaturePolicyUtils.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/PermissionManager.h"

using namespace mozilla::dom;

namespace mozilla {

typedef PermissionDelegateHandler::PermissionDelegatePolicy DelegatePolicy;
typedef PermissionDelegateHandler::PermissionDelegateInfo DelegateInfo;

// Particular type of permissions to care about. We decide cases by case and
// give various types of controls over each of these.
static const DelegateInfo sPermissionsMap[] = {
    // Permissions API map. All permission names have to be in lowercase.
    {"geo", u"geolocation", DelegatePolicy::eDelegateUseFeaturePolicy},
    // The same with geo, but we support both to save some conversions between
    // "geo" and "geolocation"
    {"geolocation", u"geolocation", DelegatePolicy::eDelegateUseFeaturePolicy},
    {"desktop-notification", nullptr,
     DelegatePolicy::ePersistDeniedCrossOrigin},
    {"persistent-storage", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
    {"vibration", nullptr, DelegatePolicy::ePersistDeniedCrossOrigin},
    {"midi", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
    // Like "midi" but with sysex support.
    {"midi-sysex", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
    {"storage-access", nullptr, DelegatePolicy::eDelegateUseIframeOrigin},
    {"camera", u"camera", DelegatePolicy::eDelegateUseFeaturePolicy},
    {"microphone", u"microphone", DelegatePolicy::eDelegateUseFeaturePolicy},
    {"screen", u"display-capture", DelegatePolicy::eDelegateUseFeaturePolicy},
    {"xr", u"xr-spatial-tracking", DelegatePolicy::eDelegateUseFeaturePolicy},
};

static_assert(PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT ==
                  (sizeof(sPermissionsMap) / sizeof(DelegateInfo)),
              "The PermissionDelegateHandler::DELEGATED_PERMISSION_COUNT must "
              "match to the "
              "length of sPermissionsMap. Please update it.");

NS_IMPL_CYCLE_COLLECTION(PermissionDelegateHandler)
NS_IMPL_CYCLE_COLLECTING_ADDREF(PermissionDelegateHandler)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PermissionDelegateHandler)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PermissionDelegateHandler)
  NS_INTERFACE_MAP_ENTRY(nsIPermissionDelegateHandler)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

PermissionDelegateHandler::PermissionDelegateHandler(dom::Document* aDocument)
    : mDocument(aDocument) {
  MOZ_ASSERT(aDocument);
}

/* static */
const DelegateInfo* PermissionDelegateHandler::GetPermissionDelegateInfo(
    const nsAString& aPermissionName) {
  nsAutoString lowerContent(aPermissionName);
  ToLowerCase(lowerContent);

  for (const auto& perm : sPermissionsMap) {
    if (lowerContent.EqualsASCII(perm.mPermissionName)) {
      return &perm;
    }
  }

  return nullptr;
}

NS_IMETHODIMP
PermissionDelegateHandler::MaybeUnsafePermissionDelegate(
    const nsTArray<nsCString>& aTypes, bool* aMaybeUnsafe) {
  *aMaybeUnsafe = false;
  if (!StaticPrefs::permissions_delegation_enabled()) {
    return NS_OK;
  }

  for (auto& type : aTypes) {
    const DelegateInfo* info =
        GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(type));
    if (!info) {
      continue;
    }

    nsAutoString featureName(info->mFeatureName);
    if (FeaturePolicyUtils::IsFeatureUnsafeAllowedAll(mDocument, featureName)) {
      *aMaybeUnsafe = true;
      return NS_OK;
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
PermissionDelegateHandler::GetPermissionDelegateFPEnabled(bool* aEnabled) {
  MOZ_ASSERT(NS_IsMainThread());
  *aEnabled = StaticPrefs::permissions_delegation_enabled();
  return NS_OK;
}

/* static */
nsresult PermissionDelegateHandler::GetDelegatePrincipal(
    const nsACString& aType, nsIContentPermissionRequest* aRequest,
    nsIPrincipal** aResult) {
  MOZ_ASSERT(aRequest);

  if (!StaticPrefs::permissions_delegation_enabled()) {
    return aRequest->GetPrincipal(aResult);
  }

  const DelegateInfo* info =
      GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
  if (!info) {
    *aResult = nullptr;
    return NS_OK;
  }

  if (info->mPolicy == DelegatePolicy::eDelegateUseTopOrigin ||
      info->mPolicy == DelegatePolicy::eDelegateUseFeaturePolicy) {
    return aRequest->GetTopLevelPrincipal(aResult);
  }

  return aRequest->GetPrincipal(aResult);
}

bool PermissionDelegateHandler::Initialize() {
  MOZ_ASSERT(mDocument);

  mPermissionManager = PermissionManager::GetInstance();
  if (!mPermissionManager) {
    return false;
  }

  mPrincipal = mDocument->NodePrincipal();
  return true;
}

static bool IsCrossOriginContentToTop(Document* aDocument) {
  MOZ_ASSERT(aDocument);

  RefPtr<BrowsingContext> bc = aDocument->GetBrowsingContext();
  if (!bc) {
    return true;
  }
  RefPtr<BrowsingContext> topBC = bc->Top();

  // In Fission, we can know if it is cross-origin by checking whether both
  // contexts in the same process. So, If they are not in the same process, we
  // can say that it's cross-origin.
  if (!topBC->IsInProcess()) {
    return true;
  }

  RefPtr<Document> topDoc = topBC->GetDocument();
  if (!topDoc) {
    return true;
  }

  nsCOMPtr<nsIPrincipal> topLevelPrincipal = topDoc->NodePrincipal();

  return !aDocument->NodePrincipal()->Subsumes(topLevelPrincipal);
}

bool PermissionDelegateHandler::HasFeaturePolicyAllowed(
    const DelegateInfo* info) const {
  if (info->mPolicy != DelegatePolicy::eDelegateUseFeaturePolicy ||
      !info->mFeatureName) {
    return true;
  }

  nsAutoString featureName(info->mFeatureName);
  return FeaturePolicyUtils::IsFeatureAllowed(mDocument, featureName);
}

bool PermissionDelegateHandler::HasPermissionDelegated(
    const nsACString& aType) const {
  MOZ_ASSERT(mDocument);

  // System principal should have right to make permission request
  if (mPrincipal->IsSystemPrincipal()) {
    return true;
  }

  const DelegateInfo* info =
      GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
  if (!info || !HasFeaturePolicyAllowed(info)) {
    return false;
  }

  if (!StaticPrefs::permissions_delegation_enabled()) {
    return true;
  }

  if (info->mPolicy == DelegatePolicy::ePersistDeniedCrossOrigin &&
      !mDocument->IsTopLevelContentDocument() &&
      IsCrossOriginContentToTop(mDocument)) {
    return false;
  }

  return true;
}

nsresult PermissionDelegateHandler::GetPermission(const nsACString& aType,
                                                  uint32_t* aPermission,
                                                  bool aExactHostMatch) {
  MOZ_ASSERT(mDocument);
  MOZ_ASSERT(mPrincipal);

  if (mPrincipal->IsSystemPrincipal()) {
    *aPermission = nsIPermissionManager::ALLOW_ACTION;
    return NS_OK;
  }

  const DelegateInfo* info =
      GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
  if (!info || !HasFeaturePolicyAllowed(info)) {
    *aPermission = nsIPermissionManager::DENY_ACTION;
    return NS_OK;
  }

  nsresult (NS_STDCALL nsIPermissionManager::*testPermission)(
      nsIPrincipal*, const nsACString&, uint32_t*) =
      aExactHostMatch ? &nsIPermissionManager::TestExactPermissionFromPrincipal
                      : &nsIPermissionManager::TestPermissionFromPrincipal;

  if (!StaticPrefs::permissions_delegation_enabled()) {
    return (mPermissionManager->*testPermission)(mPrincipal, aType,
                                                 aPermission);
  }

  if (info->mPolicy == DelegatePolicy::ePersistDeniedCrossOrigin &&
      !mDocument->IsTopLevelContentDocument() &&
      IsCrossOriginContentToTop(mDocument)) {
    *aPermission = nsIPermissionManager::DENY_ACTION;
    return NS_OK;
  }

  nsIPrincipal* principal = mPrincipal;
  // If we cannot get the browsing context from the document, we fallback to use
  // the prinicpal of the document to test the permission.
  RefPtr<BrowsingContext> bc = mDocument->GetBrowsingContext();

  if ((info->mPolicy == DelegatePolicy::eDelegateUseTopOrigin ||
       info->mPolicy == DelegatePolicy::eDelegateUseFeaturePolicy) &&
      bc) {
    RefPtr<WindowContext> topWC = bc->GetTopWindowContext();

    if (topWC && topWC->IsInProcess()) {
      // If the top-level window context is in the same process, we directly get
      // the node principal from the top-level document to test the permission.
      // We cannot check the lists in the window context in this case since the
      // 'perm-changed' could be notified in the iframe before the top-level in
      // certain cases, for example, request permissions in first-party iframes.
      // In this case, the list in window context hasn't gotten updated, so it
      // would has an out-dated value until the top-level window get the
      // observer. So, we have to test permission manager directly if we can.
      RefPtr<Document> topDoc = topWC->GetBrowsingContext()->GetDocument();

      if (topDoc) {
        principal = topDoc->NodePrincipal();
      }
    } else if (topWC) {
      // Get the delegated permissions from the top-level window context.
      DelegatedPermissionList list =
          aExactHostMatch ? topWC->GetDelegatedExactHostMatchPermissions()
                          : topWC->GetDelegatedPermissions();
      size_t idx = std::distance(sPermissionsMap, info);
      *aPermission = list.mPermissions[idx];
      return NS_OK;
    }
  }

  return (mPermissionManager->*testPermission)(principal, aType, aPermission);
}

nsresult PermissionDelegateHandler::GetPermissionForPermissionsAPI(
    const nsACString& aType, uint32_t* aPermission) {
  return GetPermission(aType, aPermission, false);
}

void PermissionDelegateHandler::PopulateAllDelegatedPermissions() {
  MOZ_ASSERT(mDocument);
  MOZ_ASSERT(mPermissionManager);

  // We only populate the delegated permissions for the top-level content.
  if (!mDocument->IsTopLevelContentDocument()) {
    return;
  }

  RefPtr<WindowContext> wc = mDocument->GetWindowContext();
  NS_ENSURE_TRUE_VOID(wc && !wc->IsDiscarded());

  DelegatedPermissionList list;
  DelegatedPermissionList exactHostMatchList;

  for (const auto& perm : sPermissionsMap) {
    size_t idx = std::distance(sPermissionsMap, &perm);

    nsDependentCString type(perm.mPermissionName);
    // Populate the permission.
    uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
    Unused << mPermissionManager->TestPermissionFromPrincipal(mPrincipal, type,
                                                              &permission);
    list.mPermissions[idx] = permission;

    // Populate the exact-host-match permission.
    permission = nsIPermissionManager::UNKNOWN_ACTION;
    Unused << mPermissionManager->TestExactPermissionFromPrincipal(
        mPrincipal, type, &permission);
    exactHostMatchList.mPermissions[idx] = permission;
  }

  WindowContext::Transaction txn;
  txn.SetDelegatedPermissions(list);
  txn.SetDelegatedExactHostMatchPermissions(exactHostMatchList);
  MOZ_ALWAYS_SUCCEEDS(txn.Commit(wc));
}

void PermissionDelegateHandler::UpdateDelegatedPermission(
    const nsACString& aType) {
  MOZ_ASSERT(mDocument);
  MOZ_ASSERT(mPermissionManager);

  // We only update the delegated permission for the top-level content.
  if (!mDocument->IsTopLevelContentDocument()) {
    return;
  }

  RefPtr<WindowContext> wc = mDocument->GetWindowContext();
  NS_ENSURE_TRUE_VOID(wc);

  const DelegateInfo* info =
      GetPermissionDelegateInfo(NS_ConvertUTF8toUTF16(aType));
  if (!info) {
    return;
  }
  size_t idx = std::distance(sPermissionsMap, info);

  WindowContext::Transaction txn;
  bool changed = false;
  DelegatedPermissionList list = wc->GetDelegatedPermissions();

  if (UpdateDelegatePermissionInternal(
          list, aType, idx,
          &nsIPermissionManager::TestPermissionFromPrincipal)) {
    txn.SetDelegatedPermissions(list);
    changed = true;
  }

  DelegatedPermissionList exactHostMatchList =
      wc->GetDelegatedExactHostMatchPermissions();

  if (UpdateDelegatePermissionInternal(
          exactHostMatchList, aType, idx,
          &nsIPermissionManager::TestExactPermissionFromPrincipal)) {
    txn.SetDelegatedExactHostMatchPermissions(exactHostMatchList);
    changed = true;
  }

  // We only commit if there is any change of permissions.
  if (changed) {
    MOZ_ALWAYS_SUCCEEDS(txn.Commit(wc));
  }
}

bool PermissionDelegateHandler::UpdateDelegatePermissionInternal(
    PermissionDelegateHandler::DelegatedPermissionList& aList,
    const nsACString& aType, size_t aIdx,
    nsresult (NS_STDCALL nsIPermissionManager::*aTestFunc)(nsIPrincipal*,
                                                           const nsACString&,
                                                           uint32_t*)) {
  MOZ_ASSERT(aTestFunc);
  MOZ_ASSERT(mPermissionManager);
  MOZ_ASSERT(mPrincipal);

  uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
  Unused << (mPermissionManager->*aTestFunc)(mPrincipal, aType, &permission);

  if (aList.mPermissions[aIdx] != permission) {
    aList.mPermissions[aIdx] = permission;
    return true;
  }

  return false;
}

}  // namespace mozilla