diff options
Diffstat (limited to 'caps/BasePrincipal.h')
-rw-r--r-- | caps/BasePrincipal.h | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h new file mode 100644 index 0000000000..9304be7dee --- /dev/null +++ b/caps/BasePrincipal.h @@ -0,0 +1,487 @@ +/* -*- 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_BasePrincipal_h +#define mozilla_BasePrincipal_h + +#include <stdint.h> +#include "ErrorList.h" +#include "js/TypeDecls.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/OriginAttributes.h" +#include "mozilla/RefPtr.h" +#include "nsAtom.h" +#include "nsIObjectOutputStream.h" +#include "nsIPrincipal.h" +#include "nsJSPrincipals.h" +#include "nsStringFwd.h" +#include "nscore.h" + +class ExpandedPrincipal; +class mozIDOMWindow; +class nsIChannel; +class nsIReferrerInfo; +class nsISupports; +class nsIURI; + +namespace mozilla { + +class JSONWriter; + +namespace dom { +enum class ReferrerPolicy : uint8_t; +} + +namespace extensions { +class WebExtensionPolicy; +class WebExtensionPolicyCore; +} // namespace extensions + +class BasePrincipal; + +// Content principals (and content principals embedded within expanded +// principals) stored in SiteIdentifier are guaranteed to contain only the +// eTLD+1 part of the original domain. This is used to determine whether two +// origins are same-site: if it's possible for two origins to access each other +// (maybe after mutating document.domain), then they must have the same site +// identifier. +class SiteIdentifier { + public: + void Init(BasePrincipal* aPrincipal) { + MOZ_ASSERT(aPrincipal); + mPrincipal = aPrincipal; + } + + bool IsInitialized() const { return !!mPrincipal; } + + bool Equals(const SiteIdentifier& aOther) const; + + private: + friend class ::ExpandedPrincipal; + + BasePrincipal* GetPrincipal() const { + MOZ_ASSERT(IsInitialized()); + return mPrincipal; + } + + RefPtr<BasePrincipal> mPrincipal; +}; + +/* + * Base class from which all nsIPrincipal implementations inherit. Use this for + * default implementations and other commonalities between principal + * implementations. + * + * We should merge nsJSPrincipals into this class at some point. + */ +class BasePrincipal : public nsJSPrincipals { + public: + // Warning: this enum impacts Principal serialization into JSON format. + // Only update if you know exactly what you are doing + enum PrincipalKind { + eNullPrincipal = 0, + eContentPrincipal, + eExpandedPrincipal, + eSystemPrincipal, + eKindMax = eSystemPrincipal + }; + + static constexpr char NullPrincipalKey = '0'; + static_assert(eNullPrincipal == 0); + static constexpr char ContentPrincipalKey = '1'; + static_assert(eContentPrincipal == 1); + static constexpr char ExpandedPrincipalKey = '2'; + static_assert(eExpandedPrincipal == 2); + static constexpr char SystemPrincipalKey = '3'; + static_assert(eSystemPrincipal == 3); + + template <typename T> + bool Is() const { + return mKind == T::Kind(); + } + + template <typename T> + T* As() { + MOZ_ASSERT(Is<T>()); + return static_cast<T*>(this); + } + + enum DocumentDomainConsideration { + DontConsiderDocumentDomain, + ConsiderDocumentDomain + }; + bool Subsumes(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration); + + NS_IMETHOD GetOrigin(nsACString& aOrigin) final; + NS_IMETHOD GetWebExposedOriginSerialization(nsACString& aOrigin) override; + NS_IMETHOD GetOriginNoSuffix(nsACString& aOrigin) final; + NS_IMETHOD Equals(nsIPrincipal* other, bool* _retval) final; + NS_IMETHOD EqualsConsideringDomain(nsIPrincipal* other, bool* _retval) final; + NS_IMETHOD EqualsURI(nsIURI* aOtherURI, bool* _retval) override; + NS_IMETHOD EqualsForPermission(nsIPrincipal* other, bool aExactHost, + bool* _retval) final; + NS_IMETHOD Subsumes(nsIPrincipal* other, bool* _retval) final; + NS_IMETHOD SubsumesConsideringDomain(nsIPrincipal* other, + bool* _retval) final; + NS_IMETHOD SubsumesConsideringDomainIgnoringFPD(nsIPrincipal* other, + bool* _retval) final; + NS_IMETHOD CheckMayLoad(nsIURI* uri, bool allowIfInheritsPrincipal) final; + NS_IMETHOD CheckMayLoadWithReporting(nsIURI* uri, + bool allowIfInheritsPrincipal, + uint64_t innerWindowID) final; + NS_IMETHOD GetAddonPolicy(extensions::WebExtensionPolicy** aResult) final; + NS_IMETHOD GetContentScriptAddonPolicy( + extensions::WebExtensionPolicy** aResult) final; + NS_IMETHOD GetIsNullPrincipal(bool* aResult) override; + NS_IMETHOD GetIsContentPrincipal(bool* aResult) override; + NS_IMETHOD GetIsExpandedPrincipal(bool* aResult) override; + NS_IMETHOD GetIsSystemPrincipal(bool* aResult) override; + NS_IMETHOD GetScheme(nsACString& aScheme) override; + NS_IMETHOD SchemeIs(const char* aScheme, bool* aResult) override; + NS_IMETHOD IsURIInPrefList(const char* aPref, bool* aResult) override; + NS_IMETHOD IsURIInList(const nsACString& aList, bool* aResult) override; + NS_IMETHOD IsContentAccessibleAboutURI(bool* aResult) override; + NS_IMETHOD IsL10nAllowed(nsIURI* aURI, bool* aResult) override; + NS_IMETHOD GetAboutModuleFlags(uint32_t* flags) override; + NS_IMETHOD GetIsAddonOrExpandedAddonPrincipal(bool* aResult) override; + NS_IMETHOD GetOriginAttributes(JSContext* aCx, + JS::MutableHandle<JS::Value> aVal) final; + NS_IMETHOD GetAsciiSpec(nsACString& aSpec) override; + NS_IMETHOD GetSpec(nsACString& aSpec) override; + NS_IMETHOD GetExposablePrePath(nsACString& aResult) override; + NS_IMETHOD GetExposableSpec(nsACString& aSpec) override; + NS_IMETHOD GetHostPort(nsACString& aRes) override; + NS_IMETHOD GetHost(nsACString& aRes) override; + NS_IMETHOD GetPrePath(nsACString& aResult) override; + NS_IMETHOD GetFilePath(nsACString& aResult) override; + NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final; + NS_IMETHOD GetIsIpAddress(bool* aIsIpAddress) override; + NS_IMETHOD GetIsLocalIpAddress(bool* aIsIpAddress) override; + NS_IMETHOD GetIsOnion(bool* aIsOnion) override; + NS_IMETHOD GetIsInIsolatedMozBrowserElement( + bool* aIsInIsolatedMozBrowserElement) final; + NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final; + NS_IMETHOD GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) final; + NS_IMETHOD GetSiteOrigin(nsACString& aSiteOrigin) final; + NS_IMETHOD GetSiteOriginNoSuffix(nsACString& aSiteOrigin) override; + NS_IMETHOD IsThirdPartyURI(nsIURI* uri, bool* aRes) override; + NS_IMETHOD IsThirdPartyPrincipal(nsIPrincipal* uri, bool* aRes) override; + NS_IMETHOD IsThirdPartyChannel(nsIChannel* aChannel, bool* aRes) override; + NS_IMETHOD GetIsOriginPotentiallyTrustworthy(bool* aResult) override; + NS_IMETHOD GetIsLoopbackHost(bool* aResult) override; + NS_IMETHOD IsSameOrigin(nsIURI* aURI, bool* aRes) override; + NS_IMETHOD GetPrefLightCacheKey(nsIURI* aURI, bool aWithCredentials, + const OriginAttributes& aOriginAttributes, + nsACString& _retval) override; + NS_IMETHOD HasFirstpartyStorageAccess(mozIDOMWindow* aCheckWindow, + uint32_t* aRejectedReason, + bool* aOutAllowed) override; + NS_IMETHOD GetAsciiHost(nsACString& aAsciiHost) override; + NS_IMETHOD GetLocalStorageQuotaKey(nsACString& aRes) override; + NS_IMETHOD AllowsRelaxStrictFileOriginPolicy(nsIURI* aURI, + bool* aRes) override; + NS_IMETHOD CreateReferrerInfo(mozilla::dom::ReferrerPolicy aReferrerPolicy, + nsIReferrerInfo** _retval) override; + NS_IMETHOD GetIsScriptAllowedByPolicy( + bool* aIsScriptAllowedByPolicy) override; + NS_IMETHOD GetStorageOriginKey(nsACString& aOriginKey) override; + + NS_IMETHOD GetNextSubDomainPrincipal( + nsIPrincipal** aNextSubDomainPrincipal) override; + + NS_IMETHOD GetPrecursorPrincipal(nsIPrincipal** aPrecursor) override; + + nsresult ToJSON(nsACString& aJSON); + nsresult ToJSON(JSONWriter& aWriter); + nsresult WriteJSONProperties(JSONWriter& aWriter); + + static already_AddRefed<BasePrincipal> FromJSON(const nsACString& aJSON); + + // Method to write serializable fields which represent all of the fields to + // deserialize the principal. + virtual nsresult WriteJSONInnerProperties(JSONWriter& aWriter); + + virtual bool AddonHasPermission(const nsAtom* aPerm); + + virtual bool IsContentPrincipal() const { return false; }; + + static BasePrincipal* Cast(nsIPrincipal* aPrin) { + return static_cast<BasePrincipal*>(aPrin); + } + + static BasePrincipal& Cast(nsIPrincipal& aPrin) { + return *static_cast<BasePrincipal*>(&aPrin); + } + + static const BasePrincipal* Cast(const nsIPrincipal* aPrin) { + return static_cast<const BasePrincipal*>(aPrin); + } + + static const BasePrincipal& Cast(const nsIPrincipal& aPrin) { + return *static_cast<const BasePrincipal*>(&aPrin); + } + + static already_AddRefed<BasePrincipal> CreateContentPrincipal( + const nsACString& aOrigin); + + // This method may not create a content principal in case it's not possible to + // generate a correct origin from the passed URI. If this happens, a + // NullPrincipal is returned. + // + // If `aInitialDomain` is specified, and a ContentPrincipal is set, it will + // initially have its domain set to the given value, without re-computing js + // wrappers. Unlike `SetDomain()` this is safe to do off-main-thread. + + static already_AddRefed<BasePrincipal> CreateContentPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs, + nsIURI* aInitialDomain = nullptr); + + const OriginAttributes& OriginAttributesRef() final { + return mOriginAttributes; + } + extensions::WebExtensionPolicy* AddonPolicy(); + RefPtr<extensions::WebExtensionPolicyCore> AddonPolicyCore(); + uint32_t UserContextId() const { return mOriginAttributes.mUserContextId; } + uint32_t PrivateBrowsingId() const { + return mOriginAttributes.mPrivateBrowsingId; + } + bool IsInIsolatedMozBrowserElement() const { + return mOriginAttributes.mInIsolatedMozBrowser; + } + + PrincipalKind Kind() const { return mKind; } + + already_AddRefed<BasePrincipal> CloneForcingOriginAttributes( + const OriginAttributes& aOriginAttributes); + + // If this is an add-on content script principal, returns its AddonPolicy. + // Otherwise returns null. + extensions::WebExtensionPolicy* ContentScriptAddonPolicy(); + RefPtr<extensions::WebExtensionPolicyCore> ContentScriptAddonPolicyCore(); + + // Helper to check whether this principal is associated with an addon that + // allows unprivileged code to load aURI. aExplicit == true will prevent + // use of all_urls permission, requiring the domain in its permissions. + bool AddonAllowsLoad(nsIURI* aURI, bool aExplicit = false); + + // Call these to avoid the cost of virtual dispatch. + inline bool FastEquals(nsIPrincipal* aOther); + inline bool FastEqualsConsideringDomain(nsIPrincipal* aOther); + inline bool FastSubsumes(nsIPrincipal* aOther); + inline bool FastSubsumesConsideringDomain(nsIPrincipal* aOther); + inline bool FastSubsumesIgnoringFPD(nsIPrincipal* aOther); + inline bool FastSubsumesConsideringDomainIgnoringFPD(nsIPrincipal* aOther); + + // Fast way to check whether we have a system principal. + inline bool IsSystemPrincipal() const; + + // Returns the principal to inherit when a caller with this principal loads + // the given URI. + // + // For most principal types, this returns the principal itself. For expanded + // principals, it returns the first sub-principal which subsumes the given URI + // (or, if no URI is given, the last allowlist principal). + nsIPrincipal* PrincipalToInherit(nsIURI* aRequestedURI = nullptr); + + /* Returns true if this principal's CSP should override a document's CSP for + * loads that it triggers. Currently true for expanded principals which + * subsume the document principal, and add-on content principals regardless + * of whether they subsume the document principal. + */ + bool OverridesCSP(nsIPrincipal* aDocumentPrincipal); + + uint32_t GetOriginNoSuffixHash() const { return mOriginNoSuffix->hash(); } + uint32_t GetOriginSuffixHash() const { return mOriginSuffix->hash(); } + + virtual nsresult GetSiteIdentifier(SiteIdentifier& aSite) = 0; + + protected: + BasePrincipal(PrincipalKind aKind, const nsACString& aOriginNoSuffix, + const OriginAttributes& aOriginAttributes); + BasePrincipal(BasePrincipal* aOther, + const OriginAttributes& aOriginAttributes); + + virtual ~BasePrincipal(); + + // Note that this does not check OriginAttributes. Callers that depend on + // those must call Subsumes instead. + virtual bool SubsumesInternal(nsIPrincipal* aOther, + DocumentDomainConsideration aConsider) = 0; + + // Internal, side-effect-free check to determine whether the concrete + // principal would allow the load ignoring any common behavior implemented in + // BasePrincipal::CheckMayLoad. + // + // Safe to call from any thread, unlike CheckMayLoad. + virtual bool MayLoadInternal(nsIURI* aURI) = 0; + friend class ::ExpandedPrincipal; + + // Helper for implementing CheckMayLoad and CheckMayLoadWithReporting. + nsresult CheckMayLoadHelper(nsIURI* aURI, bool aAllowIfInheritsPrincipal, + bool aReport, uint64_t aInnerWindowID); + + void SetHasExplicitDomain() { mHasExplicitDomain = true; } + bool GetHasExplicitDomain() { return mHasExplicitDomain; } + + // KeyValT holds a principal subtype-specific key value and the associated + // parsed value after JSON parsing. + template <typename SerializedKey> + struct KeyValT { + static_assert(sizeof(SerializedKey) == 1, + "SerializedKey should be a uint8_t"); + SerializedKey key; + bool valueWasSerialized; + nsCString value; + }; + + // Common base class for all Deserializer implementations in concrete + // subclasses. Subclasses will initialize `mPrincipal` in `Read`, and then + // calls to `QueryInterface` will QI on the target object. + class Deserializer : public nsISerializable { + public: + NS_DECL_ISUPPORTS + NS_IMETHOD Write(nsIObjectOutputStream* aStream) override; + + protected: + virtual ~Deserializer() = default; + RefPtr<BasePrincipal> mPrincipal; + }; + + private: + static constexpr Span<const char> JSONEnumKeyStrings[4] = { + MakeStringSpan("0"), + MakeStringSpan("1"), + MakeStringSpan("2"), + MakeStringSpan("3"), + }; + + static void WriteJSONProperty(JSONWriter& aWriter, + const Span<const char>& aKey, + const nsCString& aValue); + + protected: + template <size_t EnumValue> + static inline constexpr const Span<const char>& JSONEnumKeyString() { + static_assert(EnumValue < ArrayLength(JSONEnumKeyStrings)); + return JSONEnumKeyStrings[EnumValue]; + } + template <size_t EnumValue> + static void WriteJSONProperty(JSONWriter& aWriter, const nsCString& aValue) { + WriteJSONProperty(aWriter, JSONEnumKeyString<EnumValue>(), aValue); + } + + private: + static already_AddRefed<BasePrincipal> CreateContentPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs, + const nsACString& aOriginNoSuffix, nsIURI* aInitialDomain); + + bool FastSubsumesIgnoringFPD(nsIPrincipal* aOther, + DocumentDomainConsideration aConsideration); + + const RefPtr<nsAtom> mOriginNoSuffix; + const RefPtr<nsAtom> mOriginSuffix; + + const OriginAttributes mOriginAttributes; + const PrincipalKind mKind; + std::atomic<bool> mHasExplicitDomain; +}; + +inline bool BasePrincipal::FastEquals(nsIPrincipal* aOther) { + MOZ_ASSERT(aOther); + + auto other = Cast(aOther); + if (Kind() != other->Kind()) { + // Principals of different kinds can't be equal. + return false; + } + + // Two principals are considered to be equal if their origins are the same. + // If the two principals are content principals, their origin attributes + // (aka the origin suffix) must also match. + if (Kind() == eSystemPrincipal) { + return this == other; + } + + if (Kind() == eContentPrincipal || Kind() == eNullPrincipal) { + return mOriginNoSuffix == other->mOriginNoSuffix && + mOriginSuffix == other->mOriginSuffix; + } + + MOZ_ASSERT(Kind() == eExpandedPrincipal); + return mOriginNoSuffix == other->mOriginNoSuffix; +} + +inline bool BasePrincipal::FastEqualsConsideringDomain(nsIPrincipal* aOther) { + MOZ_ASSERT(aOther); + + // If neither of the principals have document.domain set, we use the fast path + // in Equals(). Otherwise, we fall back to the slow path below. + auto other = Cast(aOther); + if (!mHasExplicitDomain && !other->mHasExplicitDomain) { + return FastEquals(aOther); + } + + // Principals of different kinds can't be equal. + if (Kind() != other->Kind()) { + return false; + } + + // Only ContentPrincipals should have mHasExplicitDomain set to true, so test + // that we haven't ended up here instead of FastEquals by mistake. + MOZ_ASSERT(IsContentPrincipal(), + "Only content principals can set mHasExplicitDomain"); + + return Subsumes(aOther, ConsiderDocumentDomain) && + other->Subsumes(this, ConsiderDocumentDomain); +} + +inline bool BasePrincipal::FastSubsumes(nsIPrincipal* aOther) { + MOZ_ASSERT(aOther); + + // If two principals are equal, then they both subsume each other. + if (FastEquals(aOther)) { + return true; + } + + // Otherwise, fall back to the slow path. + return Subsumes(aOther, DontConsiderDocumentDomain); +} + +inline bool BasePrincipal::FastSubsumesConsideringDomain(nsIPrincipal* aOther) { + MOZ_ASSERT(aOther); + + // If neither of the principals have document.domain set, we hand off to + // FastSubsumes() which has fast paths for some special cases. Otherwise, we + // fall back to the slow path below. + if (!mHasExplicitDomain && !Cast(aOther)->mHasExplicitDomain) { + return FastSubsumes(aOther); + } + + return Subsumes(aOther, ConsiderDocumentDomain); +} + +inline bool BasePrincipal::FastSubsumesIgnoringFPD(nsIPrincipal* aOther) { + return FastSubsumesIgnoringFPD(aOther, DontConsiderDocumentDomain); +} + +inline bool BasePrincipal::FastSubsumesConsideringDomainIgnoringFPD( + nsIPrincipal* aOther) { + return FastSubsumesIgnoringFPD(aOther, ConsiderDocumentDomain); +} + +inline bool BasePrincipal::IsSystemPrincipal() const { + return Kind() == eSystemPrincipal; +} + +} // namespace mozilla + +inline bool nsIPrincipal::IsSystemPrincipal() const { + return mozilla::BasePrincipal::Cast(this)->IsSystemPrincipal(); +} + +#endif /* mozilla_BasePrincipal_h */ |