/// <summary>
        /// Verifies that the positive assertion data matches the results of
        /// discovery on the Claimed Identifier.
        /// </summary>
        /// <param name="relyingParty">The relying party.</param>
        /// <exception cref="ProtocolException">
        /// Thrown when the Provider is asserting that a user controls an Identifier
        /// when discovery on that Identifier contradicts what the Provider says.
        /// This would be an indication of either a misconfigured Provider or
        /// an attempt by someone to spoof another user's identity with a rogue Provider.
        /// </exception>
        private void VerifyDiscoveryMatchesAssertion(OpenIdRelyingParty relyingParty)
        {
            Logger.OpenId.Debug("Verifying assertion matches identifier discovery results...");

            // Ensure that we abide by the RP's rules regarding RequireSsl for this discovery step.
            Identifier claimedId = this.Response.ClaimedIdentifier;

            if (relyingParty.SecuritySettings.RequireSsl)
            {
                if (!claimedId.TryRequireSsl(out claimedId))
                {
                    Logger.OpenId.ErrorFormat("This site is configured to accept only SSL-protected OpenIDs, but {0} was asserted and must be rejected.", this.Response.ClaimedIdentifier);
                    ErrorUtilities.ThrowProtocol(OpenIdStrings.RequireSslNotSatisfiedByAssertedClaimedId, this.Response.ClaimedIdentifier);
                }
            }

            // While it LOOKS like we're performing discovery over HTTP again
            // Yadis.IdentifierDiscoveryCachePolicy is set to HttpRequestCacheLevel.CacheIfAvailable
            // which means that the .NET runtime is caching our discoveries for us.  This turns out
            // to be very fast and keeps our code clean and easily verifiable as correct and secure.
            // CAUTION: if this discovery is ever made to be skipped based on previous discovery
            // data that was saved to the return_to URL, be careful to verify that that information
            // is signed by the RP before it's considered reliable.  In 1.x stateless mode, this RP
            // doesn't (and can't) sign its own return_to URL, so its cached discovery information
            // is merely a hint that must be verified by performing discovery again here.
            var discoveryResults = relyingParty.Discover(claimedId);

            ErrorUtilities.VerifyProtocol(
                discoveryResults.Contains(this.Endpoint),
                OpenIdStrings.IssuedAssertionFailsIdentifierDiscovery,
                this.Endpoint,
                discoveryResults.ToStringDeferred(true));
        }
Example #2
0
        private void VerifyDiscoveryMatchesAssertionAsync_ccp(OpenIdRelyingParty relyingParty, CancellationToken cancellationToken)
        {
            Identifier claimedId = this.Response.ClaimedIdentifier;

            var discoveryResults = relyingParty.Discover(claimedId, cancellationToken);

            if (!discoveryResults.Contains(this.Endpoint))
            {
                Contract.Assume(false);
            }

            GloabalState.is_endpoint_discovered = true;
        }
		/// <summary>
		/// Performs identifier discovery, creates associations and generates authentication requests
		/// on-demand for as long as new ones can be generated based on the results of Identifier discovery.
		/// </summary>
		/// <param name="userSuppliedIdentifier">The user supplied identifier.</param>
		/// <param name="relyingParty">The relying party.</param>
		/// <param name="realm">The realm.</param>
		/// <param name="returnToUrl">The return_to base URL.</param>
		/// <param name="createNewAssociationsAsNeeded">if set to <c>true</c>, associations that do not exist between this Relying Party and the asserting Providers are created before the authentication request is created.</param>
		/// <returns>
		/// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier.
		/// Never null, but may be empty.
		/// </returns>
		internal static IEnumerable<AuthenticationRequest> Create(Identifier userSuppliedIdentifier, OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, bool createNewAssociationsAsNeeded) {
			Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null);
			Contract.Requires<ArgumentNullException>(relyingParty != null);
			Contract.Requires<ArgumentNullException>(realm != null);
			Contract.Ensures(Contract.Result<IEnumerable<AuthenticationRequest>>() != null);

			// Normalize the portion of the return_to path that correlates to the realm for capitalization.
			// (so that if a web app base path is /MyApp/, but the URL of this request happens to be
			// /myapp/login.aspx, we bump up the return_to Url to use /MyApp/ so it matches the realm.
			UriBuilder returnTo = new UriBuilder(returnToUrl);
			if (returnTo.Path.StartsWith(realm.AbsolutePath, StringComparison.OrdinalIgnoreCase) &&
				!returnTo.Path.StartsWith(realm.AbsolutePath, StringComparison.Ordinal)) {
				returnTo.Path = realm.AbsolutePath + returnTo.Path.Substring(realm.AbsolutePath.Length);
				returnToUrl = returnTo.Uri;
			}

			userSuppliedIdentifier = userSuppliedIdentifier.TrimFragment();
			if (relyingParty.SecuritySettings.RequireSsl) {
				// Rather than check for successful SSL conversion at this stage,
				// We'll wait for secure discovery to fail on the new identifier.
				if (!userSuppliedIdentifier.TryRequireSsl(out userSuppliedIdentifier)) {
					// But at least log the failure.
					Logger.OpenId.WarnFormat("RequireSsl mode is on, so discovery on insecure identifier {0} will yield no results.", userSuppliedIdentifier);
				}
			}

			if (Logger.OpenId.IsWarnEnabled && returnToUrl.Query != null) {
				NameValueCollection returnToArgs = HttpUtility.ParseQueryString(returnToUrl.Query);
				foreach (string key in returnToArgs) {
					if (OpenIdRelyingParty.IsOpenIdSupportingParameter(key)) {
						Logger.OpenId.WarnFormat("OpenID argument \"{0}\" found in return_to URL.  This can corrupt an OpenID response.", key);
					}
				}
			}

			// Throw an exception now if the realm and the return_to URLs don't match
			// as required by the provider.  We could wait for the provider to test this and
			// fail, but this will be faster and give us a better error message.
			ErrorUtilities.VerifyProtocol(realm.Contains(returnToUrl), OpenIdStrings.ReturnToNotUnderRealm, returnToUrl, realm);

			// Perform discovery right now (not deferred).
			IEnumerable<IdentifierDiscoveryResult> serviceEndpoints;
			try {
				var results = relyingParty.Discover(userSuppliedIdentifier).CacheGeneratedResults();

				// If any OP Identifier service elements were found, we must not proceed
				// to use any Claimed Identifier services, per OpenID 2.0 sections 7.3.2.2 and 11.2.
				// For a discussion on this topic, see
				// http://groups.google.com/group/dotnetopenid/browse_thread/thread/4b5a8c6b2210f387/5e25910e4d2252c8
				// Usually the Discover method we called will automatically filter this for us, but
				// just to be sure, we'll do it here as well since the RP may be configured to allow
				// these dual identifiers for assertion verification purposes.
				var opIdentifiers = results.Where(result => result.ClaimedIdentifier == result.Protocol.ClaimedIdentifierForOPIdentifier).CacheGeneratedResults();
				var claimedIdentifiers = results.Where(result => result.ClaimedIdentifier != result.Protocol.ClaimedIdentifierForOPIdentifier);
				serviceEndpoints = opIdentifiers.Any() ? opIdentifiers : claimedIdentifiers;
			} catch (ProtocolException ex) {
				Logger.Yadis.ErrorFormat("Error while performing discovery on: \"{0}\": {1}", userSuppliedIdentifier, ex);
				serviceEndpoints = Enumerable.Empty<IdentifierDiscoveryResult>();
			}

			// Filter disallowed endpoints.
			serviceEndpoints = relyingParty.SecuritySettings.FilterEndpoints(serviceEndpoints);

			// Call another method that defers request generation.
			return CreateInternal(userSuppliedIdentifier, relyingParty, realm, returnToUrl, serviceEndpoints, createNewAssociationsAsNeeded);
		}
		/// <summary>
		/// Verifies that the positive assertion data matches the results of
		/// discovery on the Claimed Identifier.
		/// </summary>
		/// <param name="relyingParty">The relying party.</param>
		/// <exception cref="ProtocolException">
		/// Thrown when the Provider is asserting that a user controls an Identifier
		/// when discovery on that Identifier contradicts what the Provider says.
		/// This would be an indication of either a misconfigured Provider or
		/// an attempt by someone to spoof another user's identity with a rogue Provider.
		/// </exception>
		private void VerifyDiscoveryMatchesAssertion(OpenIdRelyingParty relyingParty) {
			Logger.OpenId.Debug("Verifying assertion matches identifier discovery results...");

			// Ensure that we abide by the RP's rules regarding RequireSsl for this discovery step.
			Identifier claimedId = this.Response.ClaimedIdentifier;
			if (relyingParty.SecuritySettings.RequireSsl) {
				if (!claimedId.TryRequireSsl(out claimedId)) {
					Logger.OpenId.ErrorFormat("This site is configured to accept only SSL-protected OpenIDs, but {0} was asserted and must be rejected.", this.Response.ClaimedIdentifier);
					ErrorUtilities.ThrowProtocol(OpenIdStrings.RequireSslNotSatisfiedByAssertedClaimedId, this.Response.ClaimedIdentifier);
				}
			}

			// Check whether this particular identifier presents a problem with HTTP discovery
			// due to limitations in the .NET Uri class.
			UriIdentifier claimedIdUri = claimedId as UriIdentifier;
			if (claimedIdUri != null && claimedIdUri.ProblematicNormalization) {
				ErrorUtilities.VerifyProtocol(relyingParty.SecuritySettings.AllowApproximateIdentifierDiscovery, OpenIdStrings.ClaimedIdentifierDefiesDotNetNormalization);
				Logger.OpenId.WarnFormat("Positive assertion for claimed identifier {0} cannot be precisely verified under partial trust hosting due to .NET limitation.  An approximate verification will be attempted.", claimedId);
			}

			// While it LOOKS like we're performing discovery over HTTP again
			// Yadis.IdentifierDiscoveryCachePolicy is set to HttpRequestCacheLevel.CacheIfAvailable
			// which means that the .NET runtime is caching our discoveries for us.  This turns out
			// to be very fast and keeps our code clean and easily verifiable as correct and secure.
			// CAUTION: if this discovery is ever made to be skipped based on previous discovery
			// data that was saved to the return_to URL, be careful to verify that that information
			// is signed by the RP before it's considered reliable.  In 1.x stateless mode, this RP
			// doesn't (and can't) sign its own return_to URL, so its cached discovery information
			// is merely a hint that must be verified by performing discovery again here.
			var discoveryResults = relyingParty.Discover(claimedId);
			ErrorUtilities.VerifyProtocol(
				discoveryResults.Contains(this.Endpoint),
				OpenIdStrings.IssuedAssertionFailsIdentifierDiscovery,
				this.Endpoint,
				discoveryResults.ToStringDeferred(true));
		}
Example #5
0
        /// <summary>
        /// Performs identifier discovery, creates associations and generates authentication requests
        /// on-demand for as long as new ones can be generated based on the results of Identifier discovery.
        /// </summary>
        /// <param name="userSuppliedIdentifier">The user supplied identifier.</param>
        /// <param name="relyingParty">The relying party.</param>
        /// <param name="realm">The realm.</param>
        /// <param name="returnToUrl">The return_to base URL.</param>
        /// <param name="createNewAssociationsAsNeeded">if set to <c>true</c>, associations that do not exist between this Relying Party and the asserting Providers are created before the authentication request is created.</param>
        /// <returns>
        /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier.
        /// Never null, but may be empty.
        /// </returns>
        internal static IEnumerable <AuthenticationRequest> Create(Identifier userSuppliedIdentifier, OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, bool createNewAssociationsAsNeeded)
        {
            Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier");
            Requires.NotNull(relyingParty, "relyingParty");
            Requires.NotNull(realm, "realm");
            Contract.Ensures(Contract.Result <IEnumerable <AuthenticationRequest> >() != null);

            // Normalize the portion of the return_to path that correlates to the realm for capitalization.
            // (so that if a web app base path is /MyApp/, but the URL of this request happens to be
            // /myapp/login.aspx, we bump up the return_to Url to use /MyApp/ so it matches the realm.
            UriBuilder returnTo = new UriBuilder(returnToUrl);

            if (returnTo.Path.StartsWith(realm.AbsolutePath, StringComparison.OrdinalIgnoreCase) &&
                !returnTo.Path.StartsWith(realm.AbsolutePath, StringComparison.Ordinal))
            {
                returnTo.Path = realm.AbsolutePath + returnTo.Path.Substring(realm.AbsolutePath.Length);
                returnToUrl   = returnTo.Uri;
            }

            userSuppliedIdentifier = userSuppliedIdentifier.TrimFragment();
            if (relyingParty.SecuritySettings.RequireSsl)
            {
                // Rather than check for successful SSL conversion at this stage,
                // We'll wait for secure discovery to fail on the new identifier.
                if (!userSuppliedIdentifier.TryRequireSsl(out userSuppliedIdentifier))
                {
                    // But at least log the failure.
                    Logger.OpenId.WarnFormat("RequireSsl mode is on, so discovery on insecure identifier {0} will yield no results.", userSuppliedIdentifier);
                }
            }

            if (Logger.OpenId.IsWarnEnabled && returnToUrl.Query != null)
            {
                NameValueCollection returnToArgs = HttpUtility.ParseQueryString(returnToUrl.Query);
                foreach (string key in returnToArgs)
                {
                    if (OpenIdRelyingParty.IsOpenIdSupportingParameter(key))
                    {
                        Logger.OpenId.WarnFormat("OpenID argument \"{0}\" found in return_to URL.  This can corrupt an OpenID response.", key);
                    }
                }
            }

            // Throw an exception now if the realm and the return_to URLs don't match
            // as required by the provider.  We could wait for the provider to test this and
            // fail, but this will be faster and give us a better error message.
            ErrorUtilities.VerifyProtocol(realm.Contains(returnToUrl), OpenIdStrings.ReturnToNotUnderRealm, returnToUrl, realm);

            // Perform discovery right now (not deferred).
            IEnumerable <IdentifierDiscoveryResult> serviceEndpoints;

            try {
                var results = relyingParty.Discover(userSuppliedIdentifier).CacheGeneratedResults();

                // If any OP Identifier service elements were found, we must not proceed
                // to use any Claimed Identifier services, per OpenID 2.0 sections 7.3.2.2 and 11.2.
                // For a discussion on this topic, see
                // http://groups.google.com/group/dotnetopenid/browse_thread/thread/4b5a8c6b2210f387/5e25910e4d2252c8
                // Usually the Discover method we called will automatically filter this for us, but
                // just to be sure, we'll do it here as well since the RP may be configured to allow
                // these dual identifiers for assertion verification purposes.
                var opIdentifiers      = results.Where(result => result.ClaimedIdentifier == result.Protocol.ClaimedIdentifierForOPIdentifier).CacheGeneratedResults();
                var claimedIdentifiers = results.Where(result => result.ClaimedIdentifier != result.Protocol.ClaimedIdentifierForOPIdentifier);
                serviceEndpoints = opIdentifiers.Any() ? opIdentifiers : claimedIdentifiers;
            } catch (ProtocolException ex) {
                Logger.Yadis.ErrorFormat("Error while performing discovery on: \"{0}\": {1}", userSuppliedIdentifier, ex);
                serviceEndpoints = Enumerable.Empty <IdentifierDiscoveryResult>();
            }

            // Filter disallowed endpoints.
            serviceEndpoints = relyingParty.SecuritySettings.FilterEndpoints(serviceEndpoints);

            // Call another method that defers request generation.
            return(CreateInternal(userSuppliedIdentifier, relyingParty, realm, returnToUrl, serviceEndpoints, createNewAssociationsAsNeeded));
        }