Bug 1165162 - Serialize originSuffix into .origin. r=gabor,sr=sicking
authorBobby Holley <bobbyholley@gmail.com>
Thu, 14 May 2015 16:55:52 -0700
changeset 265902 85402fd3e3b44f806fbb3c846d5e07d6da23d182
parent 265901 67d44922319105840634b06f128afb8e3810870a
child 265903 cb2c0a02352edbe4103e32865e7d66c0f83c64b7
push id2189
push useratolfsen@mozilla.com
push dateThu, 21 May 2015 13:24:13 +0000
reviewersgabor, sicking
bugs1165162
milestone41.0a1
Bug 1165162 - Serialize originSuffix into .origin. r=gabor,sr=sicking We also provide an opt-out for the original behavior, and use it in various consumers that look like they need fixing up. Most of the usage here is in code with persistence considerations, where we may need some sort of migration path.
browser/components/sessionstore/SessionStorage.jsm
caps/BasePrincipal.cpp
caps/BasePrincipal.h
caps/nsIPrincipal.idl
dom/bluetooth/bluetooth2/BluetoothAdapter.cpp
dom/cache/ManagerId.cpp
dom/cache/PrincipalVerifier.cpp
dom/quota/QuotaManager.cpp
dom/requestsync/RequestSyncService.jsm
extensions/cookie/nsPermissionManager.cpp
services/mobileid/MobileIdentityManager.jsm
--- a/browser/components/sessionstore/SessionStorage.jsm
+++ b/browser/components/sessionstore/SessionStorage.jsm
@@ -69,17 +69,17 @@ let SessionStorageInternal = {
     frameTree.forEach(frame => {
       let principal = getPrincipalForFrame(docShell, frame);
       if (!principal) {
         return;
       }
 
       // Get the root domain of the current history entry
       // and use that as a key for the per-host storage data.
-      let origin = principal.jarPrefix + principal.origin;
+      let origin = principal.jarPrefix + principal.originNoSuffix;
       if (visitedOrigins.has(origin)) {
         // Don't read a host twice.
         return;
       }
 
       // Mark the current origin as visited.
       visitedOrigins.add(origin);
 
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -52,16 +52,27 @@ OriginAttributes::Deserialize(nsIObjectI
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::GetOrigin(nsACString& aOrigin)
 {
+  nsresult rv = GetOriginInternal(aOrigin);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsAutoCString suffix;
+  mOriginAttributes.CreateSuffix(suffix);
+  aOrigin.Append(suffix);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+BasePrincipal::GetOriginNoSuffix(nsACString& aOrigin)
+{
   return GetOriginInternal(aOrigin);
 }
 
 bool
 BasePrincipal::Subsumes(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration)
 {
   MOZ_RELEASE_ASSERT(aOther, "The caller is performing a nonsensical security check!");
   return SubsumesInternal(aOther, aConsideration);
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -58,16 +58,17 @@ class BasePrincipal : public nsJSPrincip
 {
 public:
   BasePrincipal() {}
 
   enum DocumentDomainConsideration { DontConsiderDocumentDomain, ConsiderDocumentDomain};
   bool Subsumes(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration);
 
   NS_IMETHOD GetOrigin(nsACString& aOrigin) final;
+  NS_IMETHOD GetOriginNoSuffix(nsACString& aOrigin) final;
   NS_IMETHOD Equals(nsIPrincipal* other, bool* _retval) final;
   NS_IMETHOD EqualsConsideringDomain(nsIPrincipal* other, bool* _retval) final;
   NS_IMETHOD Subsumes(nsIPrincipal* other, bool* _retval) final;
   NS_IMETHOD SubsumesConsideringDomain(nsIPrincipal* other, bool* _retval) final;
   NS_IMETHOD GetCsp(nsIContentSecurityPolicy** aCsp) override;
   NS_IMETHOD SetCsp(nsIContentSecurityPolicy* aCsp) override;
   NS_IMETHOD GetIsNullPrincipal(bool* aIsNullPrincipal) override;
   NS_IMETHOD GetJarPrefix(nsACString& aJarPrefix) final;
--- a/caps/nsIPrincipal.idl
+++ b/caps/nsIPrincipal.idl
@@ -15,17 +15,17 @@ struct JSPrincipals;
 
 interface nsIURI;
 interface nsIContentSecurityPolicy;
 
 [ptr] native JSContext(JSContext);
 [ptr] native JSPrincipals(JSPrincipals);
 [ptr] native PrincipalArray(nsTArray<nsCOMPtr<nsIPrincipal> >);
 
-[scriptable, builtinclass, uuid(74fb6760-4ae7-4ec7-8ac7-06817c60a93a)]
+[scriptable, builtinclass, uuid(147839d5-e799-4280-831a-dd45946385f9)]
 interface nsIPrincipal : nsISerializable
 {
     /**
      * Returns whether the other principal is equivalent to this principal.
      * Principals are considered equal if they are the same principal, or
      * they have the same origin.
      */
     boolean equals(in nsIPrincipal other);
@@ -61,25 +61,16 @@ interface nsIPrincipal : nsISerializable
     /**
      * The domain URI to which this principal pertains.
      * This is congruent with HTMLDocument.domain, and may be null.
      * Setting this has no effect on the URI.
      */
     [noscript] attribute nsIURI domain;
 
     /**
-     * The origin of this principal's codebase URI.
-     * An origin is defined as: scheme + host + port.
-     */
-    // XXXcaa this should probably be turned into an nsIURI.
-    // The system principal's origin should be some caps namespace
-    // with a chrome URI.  All of chrome should probably be the same.
-    readonly attribute ACString origin;
-
-    /**
      * Returns whether the other principal is equal to or weaker than this
      * principal. Principals are equal if they are the same object or they
      * have the same origin.
      *
      * Thus a principal always subsumes itself.
      *
      * The system principal subsumes itself and all other principals.
      *
@@ -170,16 +161,34 @@ interface nsIPrincipal : nsISerializable
      *
      * If you're looking for an easy-to-use canonical stringification of the origin
      * attributes, see |originSuffix| below.
      */
     [implicit_jscontext]
     readonly attribute jsval originAttributes;
 
     /**
+     * A canonical representation of the origin for this principal. This
+     * consists of a base string (which, for codebase principals, is of the
+     * format scheme://host:port), concatenated with |originAttributes| (see
+     * below).
+     *
+     * We maintain the invariant that principalA.equals(principalB) if and only
+     * if principalA.origin == principalB.origin.
+     */
+    readonly attribute ACString origin;
+
+    /**
+     * The base part of |origin| without the concatenation with |originSuffix|.
+     * This doesn't have the important invariants described above with |origin|,
+     * and as such should only be used for legacy situations.
+     */
+    readonly attribute ACString originNoSuffix;
+
+    /**
      * A string of the form !key1=value1&key2=value2, where each pair represents
      * an attribute with a non-default value. If all attributes have default
      * values, this is the empty string.
      *
      * The value of .originSuffix is automatically serialized into .origin, so any
      * consumers using that are automatically origin-attribute-aware. Consumers with
      * special requirements must inspect and compare .originSuffix manually.
      *
--- a/dom/bluetooth/bluetooth2/BluetoothAdapter.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothAdapter.cpp
@@ -978,17 +978,17 @@ BluetoothAdapter::IsBluetoothCertifiedAp
   // Retrieve the app status and origin for permission checking
   nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
   NS_ENSURE_TRUE(doc, false);
 
   uint16_t appStatus = nsIPrincipal::APP_STATUS_NOT_INSTALLED;
   nsAutoCString appOrigin;
 
   doc->NodePrincipal()->GetAppStatus(&appStatus);
-  doc->NodePrincipal()->GetOrigin(appOrigin);
+  doc->NodePrincipal()->GetOriginNoSuffix(appOrigin);
 
   return appStatus == nsIPrincipal::APP_STATUS_CERTIFIED &&
          appOrigin.EqualsLiteral(BLUETOOTH_APP_ORIGIN);
 }
 
 void
 BluetoothAdapter::SetAdapterState(BluetoothAdapterState aState)
 {
--- a/dom/cache/ManagerId.cpp
+++ b/dom/cache/ManagerId.cpp
@@ -28,17 +28,17 @@ ManagerId::Create(nsIPrincipal* aPrincip
   //
   // But, if we get the same QuotaManager directory for different about:
   // origins, we probably only want one Manager instance.  So, we might
   // want to start using the QM's concept of origin uniqueness here.
   //
   // TODO: consider using QuotaManager's modified origin here (bug 1112071)
 
   nsAutoCString origin;
-  nsresult rv = aPrincipal->GetOrigin(origin);
+  nsresult rv = aPrincipal->GetOriginNoSuffix(origin);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   uint32_t appId;
   rv = aPrincipal->GetAppId(&appId);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   bool inBrowserElement;
   rv = aPrincipal->GetIsInBrowserElement(&inBrowserElement);
--- a/dom/cache/PrincipalVerifier.cpp
+++ b/dom/cache/PrincipalVerifier.cpp
@@ -145,17 +145,17 @@ PrincipalVerifier::VerifyOnMainThread()
   }
 
 #ifdef DEBUG
   // Sanity check principal origin by using it to construct a URI and security
   // checking it.  Don't do this for the system principal, though, as its origin
   // is a synthetic [System Principal] string.
   if (!ssm->IsSystemPrincipal(principal)) {
     nsAutoCString origin;
-    rv = principal->GetOrigin(origin);
+    rv = principal->GetOriginNoSuffix(origin);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       DispatchToInitiatingThread(rv);
       return;
     }
     nsCOMPtr<nsIURI> uri;
     rv = NS_NewURI(getter_AddRefs(uri), origin);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       DispatchToInitiatingThread(rv);
--- a/dom/quota/QuotaManager.cpp
+++ b/dom/quota/QuotaManager.cpp
@@ -2631,17 +2631,17 @@ QuotaManager::GetInfoFromPrincipal(nsIPr
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (isNullPrincipal) {
     NS_WARNING("IndexedDB not supported from this principal!");
     return NS_ERROR_FAILURE;
   }
 
   nsCString origin;
-  rv = aPrincipal->GetOrigin(origin);
+  rv = aPrincipal->GetOriginNoSuffix(origin);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (origin.EqualsLiteral(kChromeOrigin)) {
     NS_WARNING("Non-chrome principal can't use chrome origin!");
     return NS_ERROR_FAILURE;
   }
 
   nsCString jarPrefix;
--- a/dom/requestsync/RequestSyncService.jsm
+++ b/dom/requestsync/RequestSyncService.jsm
@@ -204,17 +204,17 @@ this.RequestSyncService = {
     debug('updateSchema');
     aDb.createObjectStore(RSYNCDB_NAME, { autoIncrement: true });
   },
 
   // This method generates the key for the indexedDB object storage.
   principalToKey: function(aPrincipal) {
     return aPrincipal.appId + '|' +
            aPrincipal.isInBrowserElement + '|' +
-           aPrincipal.origin;
+           aPrincipal.originNoSuffix;
   },
 
   // Add a task to the _registrations map and create the timer if it's needed.
   addRegistration: function(aObj) {
     debug('addRegistration');
 
     let key = this.principalToKey(aObj.principal);
     if (!(key in this._registrations)) {
@@ -380,17 +380,17 @@ this.RequestSyncService = {
       aData.params.state = RSYNC_STATE_WIFIONLY;
     }
 
     aData.params.overwrittenMinInterval = 0;
 
     let dbKey = aData.task + "|" +
                 aPrincipal.appId + '|' +
                 aPrincipal.isInBrowserElement + '|' +
-                aPrincipal.origin;
+                aPrincipal.originNoSuffix;
 
     let data = { principal: aPrincipal,
                  dbKey: dbKey,
                  data: aData.params,
                  active: true };
 
     let self = this;
     this.dbTxn('readwrite', function(aStore) {
@@ -496,17 +496,17 @@ this.RequestSyncService = {
     let toSave = null;
     let self = this;
     this.forEachRegistration(function(aObj) {
       if (aObj.data.task != aData.task) {
         return;
       }
 
       if (aObj.principal.isInBrowserElement != aData.isInBrowserElement ||
-          aObj.principal.origin != aData.origin) {
+          aObj.principal.originNoSuffix != aData.origin) {
         return;
       }
 
       let app = appsService.getAppByLocalId(aObj.principal.appId);
       if (app && app.manifestURL != aData.manifestURL ||
           (!app && aData.manifestURL != "")) {
         return;
       }
@@ -544,17 +544,17 @@ this.RequestSyncService = {
 
     let task = null;
     this.forEachRegistration(function(aObj) {
       if (aObj.data.task != aData.task) {
         return;
       }
 
       if (aObj.principal.isInBrowserElement != aData.isInBrowserElement ||
-          aObj.principal.origin != aData.origin) {
+          aObj.principal.originNoSuffix != aData.origin) {
         return;
       }
 
       let app = appsService.getAppByLocalId(aObj.principal.appId);
       if (app && app.manifestURL != aData.manifestURL ||
           (!app && aData.manifestURL != "")) {
         return;
       }
@@ -589,17 +589,17 @@ this.RequestSyncService = {
              wifiOnly: aObj.wifiOnly,
              data: aObj.data };
   },
 
   createFullTaskObject: function(aObj) {
     let obj = this.createPartialTaskObject(aObj);
 
     obj.app = { manifestURL: '',
-                origin: aObj.principal.origin,
+                origin: aObj.principal.originNoSuffix,
                 isInBrowserElement: aObj.principal.isInBrowserElement };
 
     let app = appsService.getAppByLocalId(aObj.principal.appId);
     if (app) {
       obj.app.manifestURL = app.manifestURL;
     }
 
     obj.state = aObj.state;
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -151,17 +151,17 @@ GetHostForPrincipal(nsIPrincipal* aPrinc
     int32_t spart = aHost.FindChar('?', 0);
     if (spart >= 0) {
       aHost.Cut(spart, aHost.Length() - spart);
     }
     return NS_OK;
   }
 
   // Some entries like "file://" uses the origin.
-  rv = aPrincipal->GetOrigin(aHost);
+  rv = aPrincipal->GetOriginNoSuffix(aHost);
   if (NS_SUCCEEDED(rv) && !aHost.IsEmpty()) {
     return NS_OK;
   }
 
   return NS_ERROR_UNEXPECTED;
 }
 
 nsCString
--- a/services/mobileid/MobileIdentityManager.jsm
+++ b/services/mobileid/MobileIdentityManager.jsm
@@ -779,36 +779,36 @@ this.MobileIdentityManager = {
         }
 
         // We might already have credentials for the user selected icc. In
         // that case, we update the credentials store with the new origin and
         // return the credentials.
         if (promptResult.serviceId) {
           let creds = this.iccInfo[promptResult.serviceId].credentials;
           if (creds) {
-            this.credStore.add(creds.iccId, creds.msisdn, aPrincipal.origin,
+            this.credStore.add(creds.iccId, creds.msisdn, aPrincipal.originNoSuffix,
                                creds.sessionToken, this.iccIds);
             return creds;
           }
         }
 
         // Or we might already have credentials for the selected phone
         // number and so we do the same: update the credentials store with the
         // new origin and return the credentials.
         return this.credStore.getByMsisdn(promptResult.msisdn)
         .then(
           (creds) => {
             if (creds) {
-              this.credStore.add(creds.iccId, creds.msisdn, aPrincipal.origin,
+              this.credStore.add(creds.iccId, creds.msisdn, aPrincipal.originNoSuffix,
                                  creds.sessionToken, this.iccIds);
               return creds;
             }
             // Otherwise, we need to verify the new number selected by the
             // user.
-            return this.verificationFlow(promptResult, aPrincipal.origin);
+            return this.verificationFlow(promptResult, aPrincipal.originNoSuffix);
           }
         );
       }
     );
   },
 
   /*********************************************************
    * Credentials check
@@ -911,36 +911,36 @@ this.MobileIdentityManager = {
       return;
     }
 
     let _creds;
 
     // First of all we look if we already have credentials for this origin.
     // If we don't have credentials it means that it is the first time that
     // the caller requested an assertion.
-    this.credStore.getByOrigin(aPrincipal.origin)
+    this.credStore.getByOrigin(aPrincipal.originNoSuffix)
     .then(
       (creds) => {
         log.debug("creds ${creds} - ${origin}", { creds: creds,
-                                                  origin: aPrincipal.origin });
+                                                  origin: aPrincipal.originNoSuffix });
         if (!creds || !creds.sessionToken) {
           log.debug("No credentials");
           return;
         }
 
         _creds = creds;
 
         // Even if we already have credentials for this origin, the consumer
         // of the API might want to force the identity selection dialog.
         if (aOptions.forceSelection || aOptions.refreshCredentials) {
           return this.promptAndVerify(principal, manifestURL, creds)
           .then(
             (newCreds) => {
               return this.checkNewCredentials(creds, newCreds,
-                                              principal.origin);
+                                              principal.originNoSuffix);
             }
           );
         }
 
         // SIM change scenario.
 
         // It is possible that the SIM cards inserted in the device at the
         // moment of the previous verification where we obtained the credentials
@@ -974,17 +974,17 @@ this.MobileIdentityManager = {
 
         // At this point we know that the SIM associated with the credentials
         // is not present in the device any more or a new SIM has been detected,
         // so we need to ask the user what to do.
         return this.promptAndVerify(principal, manifestURL, creds)
         .then(
           (newCreds) => {
             return this.checkNewCredentials(creds, newCreds,
-                                            principal.origin);
+                                            principal.originNoSuffix);
           }
         );
       }
     )
     .then(
       (creds) => {
         // Even if we have credentails it is possible that the user has
         // removed the permission to share its mobile id with this origin, so
@@ -1004,17 +1004,17 @@ this.MobileIdentityManager = {
           return this.promptAndVerify(principal, manifestURL, creds);
         }
         return this.promptAndVerify(principal, manifestURL);
       }
     )
     .then(
       (creds) => {
         if (creds) {
-          return this.generateAssertion(creds, principal.origin);
+          return this.generateAssertion(creds, principal.originNoSuffix);
         }
         return Promise.reject(ERROR_INTERNAL_CANNOT_GENERATE_ASSERTION);
       }
     )
     .then(
       (assertion) => {
         if (!assertion) {
           return Promise.reject(ERROR_INTERNAL_CANNOT_GENERATE_ASSERTION);