1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "AddonManagerWebAPI.h"
8 
9 #include "mozilla/BasePrincipal.h"
10 #include "mozilla/dom/Navigator.h"
11 #include "mozilla/dom/NavigatorBinding.h"
12 
13 #include "mozilla/Preferences.h"
14 #include "nsGlobalWindow.h"
15 #include "xpcpublic.h"
16 
17 #include "nsIDocShell.h"
18 #include "nsIScriptObjectPrincipal.h"
19 
20 namespace mozilla {
21 using namespace mozilla::dom;
22 
IsValidHost(const nsACString & host)23 static bool IsValidHost(const nsACString& host) {
24   // This hidden pref allows users to disable mozAddonManager entirely if they
25   // want for fingerprinting resistance. Someone like Tor browser will use this
26   // pref.
27   if (Preferences::GetBool(
28           "privacy.resistFingerprinting.block_mozAddonManager")) {
29     return false;
30   }
31 
32   if (host.EqualsLiteral("addons.mozilla.org")) {
33     return true;
34   }
35 
36   // When testing allow access to the developer sites.
37   if (Preferences::GetBool("extensions.webapi.testing", false)) {
38     if (host.LowerCaseEqualsLiteral("addons.allizom.org") ||
39         host.LowerCaseEqualsLiteral("addons-dev.allizom.org") ||
40         host.LowerCaseEqualsLiteral("example.com")) {
41       return true;
42     }
43   }
44 
45   return false;
46 }
47 
48 // Checks if the given uri is secure and matches one of the hosts allowed to
49 // access the API.
IsValidSite(nsIURI * uri)50 bool AddonManagerWebAPI::IsValidSite(nsIURI* uri) {
51   if (!uri) {
52     return false;
53   }
54 
55   if (!uri->SchemeIs("https")) {
56     if (!(xpc::IsInAutomation() &&
57           Preferences::GetBool("extensions.webapi.testing.http", false))) {
58       return false;
59     }
60   }
61 
62   nsAutoCString host;
63   nsresult rv = uri->GetHost(host);
64   if (NS_FAILED(rv)) {
65     return false;
66   }
67 
68   return IsValidHost(host);
69 }
70 
71 #ifndef ANDROID
IsAPIEnabled(JSContext * aCx,JSObject * aGlobal)72 bool AddonManagerWebAPI::IsAPIEnabled(JSContext* aCx, JSObject* aGlobal) {
73   MOZ_DIAGNOSTIC_ASSERT(JS_IsGlobalObject(aGlobal));
74   nsCOMPtr<nsPIDOMWindowInner> win = xpc::WindowOrNull(aGlobal);
75   if (!win) {
76     return false;
77   }
78 
79   // Check that the current window and all parent frames are allowed access to
80   // the API.
81   while (win) {
82     nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(win);
83     if (!sop) {
84       return false;
85     }
86 
87     nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
88     if (!principal) {
89       return false;
90     }
91 
92     // Reaching a window with a system principal means we have reached
93     // privileged UI of some kind so stop at this point and allow access.
94     if (principal->IsSystemPrincipal()) {
95       return true;
96     }
97 
98     nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
99     if (!docShell) {
100       // This window has been torn down so don't allow access to the API.
101       return false;
102     }
103 
104     if (!IsValidSite(win->GetDocumentURI())) {
105       return false;
106     }
107 
108     // Checks whether there is a parent frame of the same type. This won't cross
109     // mozbrowser or chrome or fission/process boundaries.
110     nsCOMPtr<nsIDocShellTreeItem> parent;
111     nsresult rv = docShell->GetInProcessSameTypeParent(getter_AddRefs(parent));
112     if (NS_FAILED(rv)) {
113       return false;
114     }
115 
116     // No parent means we've hit a mozbrowser or chrome or process boundary.
117     if (!parent) {
118       // With Fission, a cross-origin iframe has an out-of-process parent, but
119       // DocShell knows nothing about it. We need to ask BrowsingContext here,
120       // and only allow API access if AMO is actually at the top, not framed
121       // by evilleagueofevil.com.
122       return docShell->GetBrowsingContext()->IsTopContent();
123     }
124 
125     Document* doc = win->GetDoc();
126     if (!doc) {
127       return false;
128     }
129 
130     doc = doc->GetInProcessParentDocument();
131     if (!doc) {
132       // Getting here means something has been torn down so fail safe.
133       return false;
134     }
135 
136     win = doc->GetInnerWindow();
137   }
138 
139   // Found a document with no inner window, don't grant access to the API.
140   return false;
141 }
142 #else   // We don't support mozAddonManager on Android
IsAPIEnabled(JSContext * aCx,JSObject * aGlobal)143 bool AddonManagerWebAPI::IsAPIEnabled(JSContext* aCx, JSObject* aGlobal) {
144   return false;
145 }
146 #endif  // ifndef ANDROID
147 
148 namespace dom {
149 
IsHostPermitted(const GlobalObject &,const nsAString & host)150 bool AddonManagerPermissions::IsHostPermitted(const GlobalObject& /*unused*/,
151                                               const nsAString& host) {
152   return IsValidHost(NS_ConvertUTF16toUTF8(host));
153 }
154 
155 }  // namespace dom
156 
157 }  // namespace mozilla
158