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       host.EqualsLiteral("discovery.addons.mozilla.org")) {
34     return true;
35   }
36 
37   // When testing allow access to the developer sites.
38   if (Preferences::GetBool("extensions.webapi.testing", false)) {
39     if (host.LowerCaseEqualsLiteral("addons.allizom.org") ||
40         host.LowerCaseEqualsLiteral("discovery.addons.allizom.org") ||
41         host.LowerCaseEqualsLiteral("addons-dev.allizom.org") ||
42         host.LowerCaseEqualsLiteral("discovery.addons-dev.allizom.org") ||
43         host.LowerCaseEqualsLiteral("example.com")) {
44       return true;
45     }
46   }
47 
48   return false;
49 }
50 
51 // Checks if the given uri is secure and matches one of the hosts allowed to
52 // access the API.
IsValidSite(nsIURI * uri)53 bool AddonManagerWebAPI::IsValidSite(nsIURI* uri) {
54   if (!uri) {
55     return false;
56   }
57 
58   if (!uri->SchemeIs("https")) {
59     if (!(xpc::IsInAutomation() &&
60           Preferences::GetBool("extensions.webapi.testing.http", false))) {
61       return false;
62     }
63   }
64 
65   nsAutoCString host;
66   nsresult rv = uri->GetHost(host);
67   if (NS_FAILED(rv)) {
68     return false;
69   }
70 
71   return IsValidHost(host);
72 }
73 
74 #ifndef ANDROID
IsAPIEnabled(JSContext * aCx,JSObject * aGlobal)75 bool AddonManagerWebAPI::IsAPIEnabled(JSContext* aCx, JSObject* aGlobal) {
76   MOZ_DIAGNOSTIC_ASSERT(JS_IsGlobalObject(aGlobal));
77   nsCOMPtr<nsPIDOMWindowInner> win = xpc::WindowOrNull(aGlobal);
78   if (!win) {
79     return false;
80   }
81 
82   // Check that the current window and all parent frames are allowed access to
83   // the API.
84   while (win) {
85     nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(win);
86     if (!sop) {
87       return false;
88     }
89 
90     nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
91     if (!principal) {
92       return false;
93     }
94 
95     // Reaching a window with a system principal means we have reached
96     // privileged UI of some kind so stop at this point and allow access.
97     if (principal->IsSystemPrincipal()) {
98       return true;
99     }
100 
101     nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
102     if (!docShell) {
103       // This window has been torn down so don't allow access to the API.
104       return false;
105     }
106 
107     if (!IsValidSite(win->GetDocumentURI())) {
108       return false;
109     }
110 
111     // Checks whether there is a parent frame of the same type. This won't cross
112     // mozbrowser or chrome or fission/process boundaries.
113     nsCOMPtr<nsIDocShellTreeItem> parent;
114     nsresult rv = docShell->GetInProcessSameTypeParent(getter_AddRefs(parent));
115     if (NS_FAILED(rv)) {
116       return false;
117     }
118 
119     // No parent means we've hit a mozbrowser or chrome or process boundary.
120     if (!parent) {
121       // With Fission, a cross-origin iframe has an out-of-process parent, but
122       // DocShell knows nothing about it. We need to ask BrowsingContext here,
123       // and only allow API access if AMO is actually at the top, not framed
124       // by evilleagueofevil.com.
125       return docShell->GetBrowsingContext()->IsTopContent();
126     }
127 
128     Document* doc = win->GetDoc();
129     if (!doc) {
130       return false;
131     }
132 
133     doc = doc->GetInProcessParentDocument();
134     if (!doc) {
135       // Getting here means something has been torn down so fail safe.
136       return false;
137     }
138 
139     win = doc->GetInnerWindow();
140   }
141 
142   // Found a document with no inner window, don't grant access to the API.
143   return false;
144 }
145 #else   // We don't support mozAddonManager on Android
IsAPIEnabled(JSContext * aCx,JSObject * aGlobal)146 bool AddonManagerWebAPI::IsAPIEnabled(JSContext* aCx, JSObject* aGlobal) {
147   return false;
148 }
149 #endif  // ifndef ANDROID
150 
151 namespace dom {
152 
IsHostPermitted(const GlobalObject &,const nsAString & host)153 bool AddonManagerPermissions::IsHostPermitted(const GlobalObject& /*unused*/,
154                                               const nsAString& host) {
155   return IsValidHost(NS_ConvertUTF16toUTF8(host));
156 }
157 
158 }  // namespace dom
159 
160 }  // namespace mozilla
161