/* -*- Mode: C++; tab-width: 8; 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/. */ #ifndef mozilla_PermissionManager_h #define mozilla_PermissionManager_h #include "nsIPermissionManager.h" #include "nsIAsyncShutdown.h" #include "nsIObserver.h" #include "nsWeakReference.h" #include "nsCOMPtr.h" #include "nsTHashtable.h" #include "nsTArray.h" #include "nsString.h" #include "nsHashKeys.h" #include "nsRefPtrHashtable.h" #include "mozilla/Atomics.h" #include "mozilla/Monitor.h" #include "mozilla/MozPromise.h" #include "mozilla/StaticMutex.h" #include "mozilla/ThreadBound.h" #include "mozilla/Variant.h" #include "mozilla/Vector.h" #include class mozIStorageConnection; class mozIStorageStatement; class nsIInputStream; class nsIPermission; class nsIPrefBranch; namespace IPC { struct Permission; } namespace mozilla { class OriginAttributesPattern; namespace dom { class ContentChild; } // namespace dom //////////////////////////////////////////////////////////////////////////////// class PermissionManager final : public nsIPermissionManager, public nsIObserver, public nsSupportsWeakReference, public nsIAsyncShutdownBlocker { friend class dom::ContentChild; public: class PermissionEntry { public: PermissionEntry(int64_t aID, uint32_t aType, uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime) : mID(aID), mExpireTime(aExpireTime), mModificationTime(aModificationTime), mType(aType), mPermission(aPermission), mExpireType(aExpireType), mNonSessionPermission(aPermission), mNonSessionExpireType(aExpireType), mNonSessionExpireTime(aExpireTime) {} int64_t mID; int64_t mExpireTime; int64_t mModificationTime; uint32_t mType; uint32_t mPermission; uint32_t mExpireType; uint32_t mNonSessionPermission; uint32_t mNonSessionExpireType; uint32_t mNonSessionExpireTime; }; /** * PermissionKey is the key used by PermissionHashKey hash table. */ class PermissionKey { public: static PermissionKey* CreateFromPrincipal(nsIPrincipal* aPrincipal, bool aForceStripOA, bool aScopeToSite, nsresult& aResult); static PermissionKey* CreateFromURI(nsIURI* aURI, nsresult& aResult); static PermissionKey* CreateFromURIAndOriginAttributes( nsIURI* aURI, const OriginAttributes* aOriginAttributes, bool aForceStripOA, nsresult& aResult); explicit PermissionKey(const nsACString& aOrigin) : mOrigin(aOrigin), mHashCode(HashString(aOrigin)) {} bool operator==(const PermissionKey& aKey) const { return mOrigin.Equals(aKey.mOrigin); } PLDHashNumber GetHashCode() const { return mHashCode; } NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PermissionKey) const nsCString mOrigin; const PLDHashNumber mHashCode; private: // Default ctor shouldn't be used. PermissionKey() = delete; // Dtor shouldn't be used outside of the class. ~PermissionKey(){}; }; class PermissionHashKey : public nsRefPtrHashKey { public: explicit PermissionHashKey(const PermissionKey* aPermissionKey) : nsRefPtrHashKey(aPermissionKey) {} PermissionHashKey(PermissionHashKey&& toCopy) : nsRefPtrHashKey(std::move(toCopy)), mPermissions(std::move(toCopy.mPermissions)) {} bool KeyEquals(const PermissionKey* aKey) const { return *aKey == *GetKey(); } static PLDHashNumber HashKey(const PermissionKey* aKey) { return aKey->GetHashCode(); } // Force the hashtable to use the copy constructor when shuffling entries // around, otherwise the Auto part of our AutoTArray won't be happy! enum { ALLOW_MEMMOVE = false }; inline nsTArray& GetPermissions() { return mPermissions; } inline const nsTArray& GetPermissions() const { return mPermissions; } inline int32_t GetPermissionIndex(uint32_t aType) const { for (uint32_t i = 0; i < mPermissions.Length(); ++i) if (mPermissions[i].mType == aType) return i; return -1; } inline PermissionEntry GetPermission(uint32_t aType) const { for (uint32_t i = 0; i < mPermissions.Length(); ++i) if (mPermissions[i].mType == aType) return mPermissions[i]; // unknown permission... return relevant data return PermissionEntry(-1, aType, nsIPermissionManager::UNKNOWN_ACTION, nsIPermissionManager::EXPIRE_NEVER, 0, 0); } private: AutoTArray mPermissions; }; // nsISupports NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIPERMISSIONMANAGER NS_DECL_NSIOBSERVER NS_DECL_NSIASYNCSHUTDOWNBLOCKER PermissionManager(); static already_AddRefed GetXPCOMSingleton(); static PermissionManager* GetInstance(); nsresult Init(); // enums for AddInternal() enum OperationType { eOperationNone, eOperationAdding, eOperationRemoving, eOperationChanging, eOperationReplacingDefault }; enum DBOperationType { eNoDBOperation, eWriteToDB }; enum NotifyOperationType { eDontNotify, eNotify }; // Similar to TestPermissionFromPrincipal, except that it is used only for // permissions which can never have default values. nsresult TestPermissionWithoutDefaultsFromPrincipal(nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t* aPermission); nsresult LegacyTestPermissionFromURI( nsIURI* aURI, const OriginAttributes* aOriginAttributes, const nsACString& aType, uint32_t* aPermission); nsresult RemovePermissionsWithAttributes(OriginAttributesPattern& aAttrs); /** * See `nsIPermissionManager::GetPermissionsWithKey` for more info on * permission keys. * * Get the permission key corresponding to the given Principal. This method is * intentionally infallible, as we want to provide an permission key to every * principal. Principals which don't have meaningful URIs with http://, * https://, or ftp:// schemes are given the default "" Permission Key. * * @param aPrincipal The Principal which the key is to be extracted from. * @param aForceStripOA Whether to force stripping the principals origin * attributes prior to generating the key. * @param aSiteScopePermissions Whether to prepare the key for permissions * scoped to the Principal's site, rather than origin. These are looked * up independently. Scoping of a permission is fully determined by its * type and determined by calls to the function IsSiteScopedPermission. * @param aKey A string which will be filled with the permission * key. */ static nsresult GetKeyForPrincipal(nsIPrincipal* aPrincipal, bool aForceStripOA, bool aSiteScopePermissions, nsACString& aKey); /** * See `nsIPermissionManager::GetPermissionsWithKey` for more info on * permission keys. * * Get the permission key corresponding to the given Origin. This method is * like GetKeyForPrincipal, except that it avoids creating a nsIPrincipal * object when you already have access to an origin string. * * If this method is passed a nonsensical origin string it may produce a * nonsensical permission key result. * * @param aOrigin The origin which the key is to be extracted from. * @param aForceStripOA Whether to force stripping the origins attributes * prior to generating the key. * @param aSiteScopePermissions Whether to prepare the key for permissions * scoped to the Principal's site, rather than origin. These are looked * up independently. Scoping of a permission is fully determined by its * type and determined by calls to the function IsSiteScopedPermission. * @param aKey A string which will be filled with the permission * key. */ static nsresult GetKeyForOrigin(const nsACString& aOrigin, bool aForceStripOA, bool aSiteScopePermissions, nsACString& aKey); /** * See `nsIPermissionManager::GetPermissionsWithKey` for more info on * permission keys. * * Get the permission key corresponding to the given Principal and type. This * method is intentionally infallible, as we want to provide an permission key * to every principal. Principals which don't have meaningful URIs with * http://, https://, or ftp:// schemes are given the default "" Permission * Key. * * This method is different from GetKeyForPrincipal in that it also takes * permissions which must be sent down before loading a document into account. * * @param aPrincipal The Principal which the key is to be extracted from. * @param aType The type of the permission to get the key for. * @param aPermissionKey A string which will be filled with the permission * key. */ static nsresult GetKeyForPermission(nsIPrincipal* aPrincipal, const nsACString& aType, nsACString& aKey); /** * See `nsIPermissionManager::GetPermissionsWithKey` for more info on * permission keys. * * Get all permissions keys which could correspond to the given principal. * This method, like GetKeyForPrincipal, is infallible and should always * produce at least one (key, origin) pair. * * Unlike GetKeyForPrincipal, this method also gets the keys for base domains * of the given principal. All keys returned by this method must be available * in the content process for a given URL to successfully have its permissions * checked in the `aExactHostMatch = false` situation. * * @param aPrincipal The Principal which the key is to be extracted from. * @return returns an array of (key, origin) pairs. */ static nsTArray> GetAllKeysForPrincipal( nsIPrincipal* aPrincipal); // From ContentChild. nsresult RemoveAllFromIPC(); /** * Returns false if this permission manager wouldn't have the permission * requested available. * * If aType is empty, checks that the permission manager would have all * permissions available for the given principal. */ bool PermissionAvailable(nsIPrincipal* aPrincipal, const nsACString& aType); /** * The content process doesn't have access to every permission. Instead, when * LOAD_DOCUMENT_URI channels for http://, https://, and ftp:// URIs are * opened, the permissions for those channels are sent down to the content * process before the OnStartRequest message. Permissions for principals with * other schemes are sent down at process startup. * * Permissions are keyed and grouped by "Permission Key"s. * `PermissionManager::GetKeyForPrincipal` provides the mechanism for * determining the permission key for a given principal. * * This method may only be called in the parent process. It fills the nsTArray * argument with the IPC::Permission objects which have a matching origin. * * @param origin The origin to use to find the permissions of interest. * @param key The key to use to find the permissions of interest. Only used * when the origin argument is empty. * @param perms An array which will be filled with the permissions which * match the given origin. */ bool GetPermissionsFromOriginOrKey(const nsACString& aOrigin, const nsACString& aKey, nsTArray& aPerms); /** * See `PermissionManager::GetPermissionsWithKey` for more info on * Permission keys. * * `SetPermissionsWithKey` may only be called in the Child process, and * initializes the permission manager with the permissions for a given * Permission key. marking permissions with that key as available. * * @param permissionKey The key for the permissions which have been sent * over. * @param perms An array with the permissions which match the given key. */ void SetPermissionsWithKey(const nsACString& aPermissionKey, nsTArray& aPerms); /** * Add a callback which should be run when all permissions are available for * the given nsIPrincipal. This method invokes the callback runnable * synchronously when the permissions are already available. Otherwise the * callback will be run asynchronously in SystemGroup when all permissions * are available in the future. * * NOTE: This method will not request the permissions be sent by the parent * process. This should only be used to wait for permissions which may not * have arrived yet in order to ensure they are present. * * @param aPrincipal The principal to wait for permissions to be available * for. * @param aRunnable The runnable to run when permissions are available for * the given principal. */ void WhenPermissionsAvailable(nsIPrincipal* aPrincipal, nsIRunnable* aRunnable); /** * Strip origin attributes for permissions, depending on permission isolation * pref state. * @param aForceStrip If true, strips user context and private browsing id, * ignoring permission isolation prefs. * @param aOriginAttributes object to strip. */ static void MaybeStripOriginAttributes(bool aForceStrip, OriginAttributes& aOriginAttributes); private: ~PermissionManager(); static StaticMutex sCreationMutex MOZ_UNANNOTATED; /** * Get all permissions for a given principal, which should not be isolated * by user context or private browsing. The principal has its origin * attributes stripped before perm db lookup. This is currently only affects * the "cookie" permission. * @param aPrincipal Used for creating the permission key. * @param aSiteScopePermissions Used to specify whether to get strip perms for * site scoped permissions (defined in IsSiteScopedPermission) or all other * permissions. Also used to create the permission key. */ nsresult GetStripPermsForPrincipal(nsIPrincipal* aPrincipal, bool aSiteScopePermissions, nsTArray& aResult); // Returns -1 on failure int32_t GetTypeIndex(const nsACString& aType, bool aAdd); // Returns whether the given combination of expire type and expire time are // expired. Note that EXPIRE_SESSION only honors expireTime if it is nonzero. bool HasExpired(uint32_t aExpireType, int64_t aExpireTime); // Appends the permissions associated with this principal to aResult. // If the onlySiteScopePermissions argument is true, the permissions searched // are those for the site of the principal and only the permissions that are // site-scoped are used. nsresult GetAllForPrincipalHelper(nsIPrincipal* aPrincipal, bool aSiteScopePermissions, nsTArray>& aResult); // Returns PermissionHashKey for a given { host, isInBrowserElement } tuple. // This is not simply using PermissionKey because we will walk-up domains in // case of |host| contains sub-domains. Returns null if nothing found. Also // accepts host on the format "". This will perform an exact match lookup // as the string doesn't contain any dots. PermissionHashKey* GetPermissionHashKey(nsIPrincipal* aPrincipal, uint32_t aType, bool aExactHostMatch); // Returns PermissionHashKey for a given { host, isInBrowserElement } tuple. // This is not simply using PermissionKey because we will walk-up domains in // case of |host| contains sub-domains. Returns null if nothing found. Also // accepts host on the format "". This will perform an exact match lookup // as the string doesn't contain any dots. PermissionHashKey* GetPermissionHashKey( nsIURI* aURI, const OriginAttributes* aOriginAttributes, uint32_t aType, bool aExactHostMatch); // The int32_t is the type index, the nsresult is an early bail-out return // code. typedef Variant TestPreparationResult; TestPreparationResult CommonPrepareToTestPermission( nsIPrincipal* aPrincipal, int32_t aTypeIndex, const nsACString& aType, uint32_t* aPermission, uint32_t aDefaultPermission, bool aDefaultPermissionIsValid, bool aExactHostMatch, bool aIncludingSession); // If aTypeIndex is passed -1, we try to inder the type index from aType. nsresult CommonTestPermission(nsIPrincipal* aPrincipal, int32_t aTypeIndex, const nsACString& aType, uint32_t* aPermission, uint32_t aDefaultPermission, bool aDefaultPermissionIsValid, bool aExactHostMatch, bool aIncludingSession); // If aTypeIndex is passed -1, we try to inder the type index from aType. nsresult CommonTestPermission(nsIURI* aURI, int32_t aTypeIndex, const nsACString& aType, uint32_t* aPermission, uint32_t aDefaultPermission, bool aDefaultPermissionIsValid, bool aExactHostMatch, bool aIncludingSession); nsresult CommonTestPermission(nsIURI* aURI, const OriginAttributes* aOriginAttributes, int32_t aTypeIndex, const nsACString& aType, uint32_t* aPermission, uint32_t aDefaultPermission, bool aDefaultPermissionIsValid, bool aExactHostMatch, bool aIncludingSession); // Only one of aPrincipal or aURI is allowed to be passed in. nsresult CommonTestPermissionInternal( nsIPrincipal* aPrincipal, nsIURI* aURI, const OriginAttributes* aOriginAttributes, int32_t aTypeIndex, const nsACString& aType, uint32_t* aPermission, bool aExactHostMatch, bool aIncludingSession); nsresult OpenDatabase(nsIFile* permissionsFile); void InitDB(bool aRemoveFile); nsresult TryInitDB(bool aRemoveFile, nsIInputStream* aDefaultsInputStream); void AddIdleDailyMaintenanceJob(); void RemoveIdleDailyMaintenanceJob(); void PerformIdleDailyMaintenance(); nsresult ImportLatestDefaults(); already_AddRefed GetDefaultsInputStream(); void ConsumeDefaultsInputStream(nsIInputStream* aDefaultsInputStream, const MonitorAutoLock& aProofOfLock); nsresult CreateTable(); void NotifyObserversWithPermission(nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime, const char16_t* aData); void NotifyObservers(nsIPermission* aPermission, const char16_t* aData); // Finalize all statements, close the DB and null it. enum CloseDBNextOp { eNone, eRebuldOnSuccess, eShutdown, }; void CloseDB(CloseDBNextOp aNextOp); nsresult RemoveAllInternal(bool aNotifyObservers); nsresult RemoveAllFromMemory(); void UpdateDB(OperationType aOp, int64_t aID, const nsACString& aOrigin, const nsACString& aType, uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime); /** * This method removes all permissions modified after the specified time. */ nsresult RemoveAllModifiedSince(int64_t aModificationTime); template nsresult RemovePermissionEntries(T aCondition); template nsresult GetPermissionEntries(T aCondition, nsTArray>& aResult); // This method must be called before doing any operation to be sure that the // DB reading has been completed. This method is also in charge to complete // the migrations if needed. void EnsureReadCompleted(); nsresult AddInternal(nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t aPermission, int64_t aID, uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime, NotifyOperationType aNotifyOperation, DBOperationType aDBOperation, const bool aIgnoreSessionPermissions = false, const nsACString* aOriginString = nullptr, const bool aAllowPersistInPrivateBrowsing = false); void MaybeAddReadEntryFromMigration(const nsACString& aOrigin, const nsCString& aType, uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime, int64_t aId); nsCOMPtr GetAsyncShutdownBarrier() const; void MaybeCompleteShutdown(); nsRefPtrHashtable mPermissionKeyPromiseMap; nsCOMPtr mPermissionsFile; // This monitor is used to ensure the database reading before any other // operation. The reading of the database happens OMT. See |State| to know the // steps of the database reading. Monitor mMonitor MOZ_UNANNOTATED; enum State { // Initial state. The database has not been read yet. // |TryInitDB| is called at startup time to read the database OMT. // During the reading, |mReadEntries| will be populated with all the // existing permissions. eInitializing, // At the end of the database reading, we are in this state. A runnable is // executed to call |EnsureReadCompleted| on the main thread. // |EnsureReadCompleted| processes |mReadEntries| and goes to the next // state. eDBInitialized, // The permissions are fully read and any pending operation can proceed. eReady, // The permission manager has been terminated. No extra database operations // will be allowed. eClosed, }; Atomic mState; // A single entry, from the database. struct ReadEntry { ReadEntry() : mId(0), mPermission(0), mExpireType(0), mExpireTime(0), mModificationTime(0) {} nsCString mOrigin; nsCString mType; int64_t mId; uint32_t mPermission; uint32_t mExpireType; int64_t mExpireTime; int64_t mModificationTime; // true if this entry is the result of a migration. bool mFromMigration; }; // List of entries read from the database. It will be populated OMT and // consumed on the main-thread. // This array is protected by the monitor. nsTArray mReadEntries; // A single entry, from the database. struct MigrationEntry { MigrationEntry() : mId(0), mPermission(0), mExpireType(0), mExpireTime(0), mModificationTime(0), mIsInBrowserElement(false) {} nsCString mHost; nsCString mType; int64_t mId; uint32_t mPermission; uint32_t mExpireType; int64_t mExpireTime; int64_t mModificationTime; // Legacy, for migration. bool mIsInBrowserElement; }; // List of entries read from the database. It will be populated OMT and // consumed on the main-thread. The migration entries will be converted to // ReadEntry in |CompleteMigrations|. // This array is protected by the monitor. nsTArray mMigrationEntries; // A single entry from the defaults URL. struct DefaultEntry { DefaultEntry() : mOp(eImportMatchTypeHost), mPermission(0) {} enum Op { eImportMatchTypeHost, eImportMatchTypeOrigin, }; Op mOp; nsCString mHostOrOrigin; nsCString mType; uint32_t mPermission; }; // List of entries read from the default settings. // This array is protected by the monitor. nsTArray mDefaultEntries; nsresult Read(const MonitorAutoLock& aProofOfLock); void CompleteRead(); void CompleteMigrations(); bool mMemoryOnlyDB; nsTHashtable mPermissionTable; // a unique, monotonically increasing id used to identify each database entry int64_t mLargestID; nsCOMPtr mDefaultPrefBranch; // NOTE: Ensure this is the last member since it has a large inline buffer. // An array to store the strings identifying the different types. Vector mTypeArray; nsCOMPtr mThread; struct ThreadBoundData { nsCOMPtr mDBConn; nsCOMPtr mStmtInsert; nsCOMPtr mStmtDelete; nsCOMPtr mStmtUpdate; }; ThreadBound mThreadBoundData; friend class DeleteFromMozHostListener; friend class CloseDatabaseListener; }; // {4F6B5E00-0C36-11d5-A535-0010A401EB10} #define NS_PERMISSIONMANAGER_CID \ { \ 0x4f6b5e00, 0xc36, 0x11d5, { \ 0xa5, 0x35, 0x0, 0x10, 0xa4, 0x1, 0xeb, 0x10 \ } \ } } // namespace mozilla #endif // mozilla_PermissionManager_h