diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2009-04-02 08:24:03 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2009-04-22 10:14:18 -0700 |
commit | 9d81aa7b9af72a96b8dced55b69e23f9b757a041 (patch) | |
tree | 85be68bd39c50d6b72530852dfbbb76e99b536c7 /src | |
parent | ae9b96762c68a7d97190a3b281800b60ab684fd4 (diff) | |
download | DotNetOpenAuth-9d81aa7b9af72a96b8dced55b69e23f9b757a041.zip DotNetOpenAuth-9d81aa7b9af72a96b8dced55b69e23f9b757a041.tar.gz DotNetOpenAuth-9d81aa7b9af72a96b8dced55b69e23f9b757a041.tar.bz2 |
More contract work.
Diffstat (limited to 'src')
33 files changed, 409 insertions, 166 deletions
diff --git a/src/DotNetOpenAuth/Configuration/AssociationTypeCollection.cs b/src/DotNetOpenAuth/Configuration/AssociationTypeCollection.cs index 56c7389..881fcdb 100644 --- a/src/DotNetOpenAuth/Configuration/AssociationTypeCollection.cs +++ b/src/DotNetOpenAuth/Configuration/AssociationTypeCollection.cs @@ -54,8 +54,7 @@ namespace DotNetOpenAuth.Configuration { /// An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>. /// </returns> protected override object GetElementKey(ConfigurationElement element) { - Contract.Assume(element != null); // this should be Contract.Requires in base class. - return ((AssociationTypeElement)element).AssociationType; + return ((AssociationTypeElement)element).AssociationType ?? string.Empty; } } } diff --git a/src/DotNetOpenAuth/Configuration/HostNameOrRegexCollection.cs b/src/DotNetOpenAuth/Configuration/HostNameOrRegexCollection.cs index 88a1615..c7d963b 100644 --- a/src/DotNetOpenAuth/Configuration/HostNameOrRegexCollection.cs +++ b/src/DotNetOpenAuth/Configuration/HostNameOrRegexCollection.cs @@ -64,7 +64,7 @@ namespace DotNetOpenAuth.Configuration { /// </returns> protected override object GetElementKey(ConfigurationElement element) { Contract.Assume(element != null); // this should be Contract.Requires in base class. - return ((HostNameElement)element).Name; + return ((HostNameElement)element).Name ?? string.Empty; } } } diff --git a/src/DotNetOpenAuth/Configuration/OpenIdElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdElement.cs index dd48175..b272024 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdElement.cs @@ -55,7 +55,9 @@ namespace DotNetOpenAuth.Configuration { internal TimeSpan MaxAuthenticationTime { get { Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero); - return (TimeSpan)this[MaxAuthenticationTimePropertyName]; + TimeSpan result = (TimeSpan)this[MaxAuthenticationTimePropertyName]; + Contract.Assume(result > TimeSpan.Zero); // our PositiveTimeSpanValidator should take care of this + return result; } set { diff --git a/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs b/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs index 022ef40..20cd332 100644 --- a/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs +++ b/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs @@ -9,13 +9,15 @@ namespace DotNetOpenAuth.Configuration { using System.Configuration; using System.Reflection; using DotNetOpenAuth.Messaging; + using System.Diagnostics.Contracts; /// <summary> /// Represents an element in a .config file that allows the user to provide a @type attribute specifying /// the full type that provides some service used by this library. /// </summary> /// <typeparam name="T">A constraint on the type the user may provide.</typeparam> - internal class TypeConfigurationElement<T> : ConfigurationElement { + internal class TypeConfigurationElement<T> : ConfigurationElement + where T : class { /// <summary> /// The name of the attribute whose value is the full name of the type the user is specifying. /// </summary> @@ -51,6 +53,8 @@ namespace DotNetOpenAuth.Configuration { /// <param name="defaultValue">The value to return if no type is given in the .config file.</param> /// <returns>The newly instantiated type.</returns> public T CreateInstance(T defaultValue) { + Contract.Ensures(Contract.Result<T>() != null || Contract.Result<T>() == defaultValue); + return this.CreateInstance(defaultValue, false); } @@ -61,6 +65,8 @@ namespace DotNetOpenAuth.Configuration { /// <param name="allowInternals">if set to <c>true</c> then internal types may be instantiated.</param> /// <returns>The newly instantiated type.</returns> public T CreateInstance(T defaultValue, bool allowInternals) { + Contract.Ensures(Contract.Result<T>() != null || Contract.Result<T>() == defaultValue); + if (this.CustomType != null) { if (!allowInternals) { // Although .NET will usually prevent our instantiating non-public types, diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 859b0c6..ec37b14 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -110,14 +110,14 @@ </CodeContractsLibPaths> <CodeContractsPlatformPath> </CodeContractsPlatformPath> - <CodeContractsExtraAnalysisOptions> - </CodeContractsExtraAnalysisOptions> + <CodeContractsExtraAnalysisOptions>-maxpathsize 15</CodeContractsExtraAnalysisOptions> <CodeContractsBaseLineFile> </CodeContractsBaseLineFile> <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> <CodeContractsRunInBackground>True</CodeContractsRunInBackground> <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies> <RunCodeAnalysis>true</RunCodeAnalysis> + <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> </PropertyGroup> <ItemGroup> <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL"> @@ -215,6 +215,7 @@ <Compile Include="Messaging\EmptyList.cs" /> <Compile Include="Messaging\ErrorUtilities.cs" /> <Compile Include="Messaging\IMessageWithEvents.cs" /> + <Compile Include="Messaging\IncomingWebResponseContract.cs" /> <Compile Include="Messaging\IProtocolMessageWithExtensions.cs" /> <Compile Include="Messaging\InternalErrorException.cs" /> <Compile Include="Messaging\KeyedCollectionDelegate.cs" /> @@ -318,6 +319,7 @@ <Compile Include="OpenId\ChannelElements\OpenIdMessageFactory.cs" /> <Compile Include="OpenId\ChannelElements\ReturnToSignatureBindingElement.cs" /> <Compile Include="OpenId\ChannelElements\SkipSecurityBindingElement.cs" /> + <Compile Include="OpenId\AssociationContract.cs" /> <Compile Include="OpenId\Extensions\AliasManager.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\AttributeRequest.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\AttributeValues.cs" /> @@ -538,4 +540,4 @@ </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" /> -</Project>
\ No newline at end of file +</Project> diff --git a/src/DotNetOpenAuth/InfoCard/ClaimType.cs b/src/DotNetOpenAuth/InfoCard/ClaimType.cs index 96a3d32..f9c4917 100644 --- a/src/DotNetOpenAuth/InfoCard/ClaimType.cs +++ b/src/DotNetOpenAuth/InfoCard/ClaimType.cs @@ -7,9 +7,9 @@ namespace DotNetOpenAuth.InfoCard { using System; using System.ComponentModel; + using System.Diagnostics.Contracts; using System.IdentityModel.Claims; using System.Web.UI; -using System.Diagnostics.Contracts; /// <summary> /// Description of a claim that is requested or required in a submitted Information Card. diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs index b787300..6e54746 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs +++ b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs @@ -583,6 +583,8 @@ namespace DotNetOpenAuth.InfoCard { /// <returns>A control that renders to the <object> tag.</returns> [Pure] private Control CreateInfoCardSelectorObject() { + Contract.Ensures(Contract.Result<Control>() != null); + HtmlGenericControl cardSpaceControl = new HtmlGenericControl(HtmlTextWriterTag.Object.ToString()); cardSpaceControl.Attributes.Add(HtmlTextWriterAttribute.Type.ToString(), "application/x-informationcard"); cardSpaceControl.Attributes.Add(HtmlTextWriterAttribute.Id.ToString(), this.ClientID + "_cs"); @@ -649,10 +651,10 @@ namespace DotNetOpenAuth.InfoCard { string[] requiredClaimsArray = requiredClaims.ToArray(); string[] optionalClaimsArray = optionalClaims.ToArray(); - Contract.Assume(requiredClaimsArray != null); - Contract.Assume(optionalClaimsArray != null); required = string.Join(" ", requiredClaimsArray); optional = string.Join(" ", optionalClaimsArray); + Contract.Assume(required != null); + Contract.Assume(optional != null); } /// <summary> diff --git a/src/DotNetOpenAuth/InfoCard/Token/Token.cs b/src/DotNetOpenAuth/InfoCard/Token/Token.cs index f07c555..f66f4ce 100644 --- a/src/DotNetOpenAuth/InfoCard/Token/Token.cs +++ b/src/DotNetOpenAuth/InfoCard/Token/Token.cs @@ -42,19 +42,21 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="decryptor">The decryptor to use to decrypt the token, if necessary..</param> /// <exception cref="InformationCardException">Thrown for any problem decoding or decrypting the token.</exception> private Token(string tokenXml, Uri audience, TokenDecryptor decryptor) { - Contract.Requires(tokenXml != null && tokenXml.Length > 0); + Contract.Requires(!String.IsNullOrEmpty(tokenXml)); Contract.Requires(decryptor != null || !IsEncrypted(tokenXml)); - ErrorUtilities.VerifyNonZeroLength(tokenXml, "tokenXml"); + Contract.Ensures(this.AuthorizationContext != null); byte[] decryptedBytes; string decryptedString; using (XmlReader tokenReader = XmlReader.Create(new StringReader(tokenXml))) { + Contract.Assume(tokenReader != null); // BCL contract should say XmlReader.Create result != null if (IsEncrypted(tokenReader)) { Logger.InfoCard.DebugFormat("Incoming SAML token, before decryption: {0}", tokenXml); ErrorUtilities.VerifyArgumentNotNull(decryptor, "decryptor"); decryptedBytes = decryptor.DecryptToken(tokenReader); decryptedString = Encoding.UTF8.GetString(decryptedBytes); + Contract.Assume(decryptedString != null); // BCL contracts should be enhanced here } else { decryptedBytes = Encoding.UTF8.GetBytes(tokenXml); decryptedString = tokenXml; @@ -106,7 +108,7 @@ namespace DotNetOpenAuth.InfoCard { /// </summary> public string SiteSpecificId { get { - Contract.Requires(this.Claims.ContainsKey(ClaimTypes.PPID)); + Contract.Requires(this.Claims.ContainsKey(ClaimTypes.PPID) && !string.IsNullOrEmpty(this.Claims[ClaimTypes.PPID])); ErrorUtilities.VerifyOperation(this.Claims.ContainsKey(ClaimTypes.PPID), InfoCardStrings.PpidClaimRequired); return TokenUtility.CalculateSiteSpecificID(this.Claims[ClaimTypes.PPID]); } @@ -197,6 +199,18 @@ namespace DotNetOpenAuth.InfoCard { } } +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() + { + Contract.Invariant(this.AuthorizationContext != null); + } +#endif + /// <summary> /// Determines whether the specified token XML is encrypted. /// </summary> diff --git a/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs b/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs index 85b951d..e58f051 100644 --- a/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs +++ b/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs @@ -48,6 +48,8 @@ namespace DotNetOpenAuth.InfoCard { /// The authorization context carried by the token. /// </returns> internal static AuthorizationContext AuthenticateToken(XmlReader reader, Uri audience) { + Contract.Ensures(Contract.Result<AuthorizationContext>() != null); + // Extensibility Point: // in order to accept different token types, you would need to add additional // code to create an authenticationcontext from the security token. @@ -218,7 +220,6 @@ namespace DotNetOpenAuth.InfoCard { /// <returns>A string containing the XXX-XXXX-XXX cosmetic value.</returns> internal static string CalculateSiteSpecificID(string ppid) { Contract.Requires(ppid != null); - ErrorUtilities.VerifyArgumentNotNull(ppid, "ppid"); Contract.Ensures(Contract.Result<string>() != null && Contract.Result<string>().Length > 0); int callSignChars = 10; diff --git a/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs b/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs index 0a24cea..e586ca8 100644 --- a/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs +++ b/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs @@ -37,6 +37,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="maximumBytesToRead">The maximum bytes to read.</param> internal CachedDirectWebResponse(Uri requestUri, HttpWebResponse response, int maximumBytesToRead) : base(requestUri, response) { + Contract.RequiresAlways(requestUri != null); + Contract.RequiresAlways(response != null); this.responseStream = CacheNetworkStreamAndClose(response, maximumBytesToRead); // BUGBUG: if the response was exactly maximumBytesToRead, we'll incorrectly believe it was truncated. @@ -55,6 +57,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="responseStream">The response stream.</param> internal CachedDirectWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding, MemoryStream responseStream) : base(requestUri, responseUri, headers, statusCode, contentType, contentEncoding) { + Contract.RequiresAlways(requestUri != null); Contract.Requires(responseStream != null); this.responseStream = responseStream; } diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index 0813d17..e2b7adc 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -441,6 +441,10 @@ namespace DotNetOpenAuth.Messaging { [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly call should not be a property.")] protected internal virtual HttpRequestInfo GetRequestFromContext() { Contract.Ensures(Contract.Result<HttpRequestInfo>() != null); + Contract.Ensures(Contract.Result<HttpRequestInfo>().Url != null); + Contract.Ensures(Contract.Result<HttpRequestInfo>().RawUrl != null); + Contract.Ensures(Contract.Result<HttpRequestInfo>().UrlBeforeRewriting != null); + ErrorUtilities.VerifyHttpContext(); return new HttpRequestInfo(HttpContext.Current.Request); @@ -591,7 +595,9 @@ namespace DotNetOpenAuth.Messaging { Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Assert(message != null && message.Recipient != null); var messageAccessor = this.MessageDescriptions.GetAccessor(message); + Contract.Assert(message != null && message.Recipient != null); var fields = messageAccessor.Serialize(); // First try creating a 301 redirect, and fallback to a form POST @@ -777,7 +783,7 @@ namespace DotNetOpenAuth.Messaging { /// </remarks> protected virtual HttpWebRequest InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) { Contract.Requires(requestMessage != null); - ErrorUtilities.VerifyArgumentNotNull(requestMessage, "requestMessage"); + Contract.Requires(requestMessage.Recipient != null); var messageAccessor = this.MessageDescriptions.GetAccessor(requestMessage); var fields = messageAccessor.Serialize(); diff --git a/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs b/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs index 1c27d6c..9f2a514 100644 --- a/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs @@ -350,7 +350,8 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> [Pure] internal static void VerifyArgumentNotNull(object value, string paramName) { - Contract.Requires(value != null); + Contract.Ensures(value != null); + Contract.EnsuresOnThrow<ArgumentNullException>(value == null); if (value == null) { throw new ArgumentNullException(paramName); } @@ -379,7 +380,8 @@ namespace DotNetOpenAuth.Messaging { [Pure] internal static void VerifyHttpContext() { Contract.Ensures(HttpContext.Current != null); - ErrorUtilities.VerifyOperation(HttpContext.Current != null, MessagingStrings.HttpContextRequired); + Contract.Ensures(HttpContext.Current.Request != null); + ErrorUtilities.VerifyOperation(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); } } } diff --git a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs index e84a097..30b4260 100644 --- a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs +++ b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs @@ -49,7 +49,16 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="request">The ASP.NET structure to copy from.</param> public HttpRequestInfo(HttpRequest request) { - Contract.Requires(request != null); + Contract.RequiresAlways(request != null); + Contract.Ensures(this.HttpMethod == request.HttpMethod); + Contract.Ensures(this.Url == request.Url); + Contract.Ensures(this.RawUrl == request.RawUrl); + Contract.Ensures(this.UrlBeforeRewriting != null); + Contract.Ensures(this.Headers != null); + Contract.Ensures(this.InputStream == request.InputStream); + Contract.Ensures(this.form == request.Form); + Contract.Ensures(this.queryString == request.QueryString); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); this.HttpMethod = request.HttpMethod; @@ -76,14 +85,10 @@ namespace DotNetOpenAuth.Messaging { /// <param name="headers">Headers in the HTTP request.</param> /// <param name="inputStream">The entity stream, if any. (POST requests typically have these). Use <c>null</c> for GET requests.</param> public HttpRequestInfo(string httpMethod, Uri requestUrl, string rawUrl, WebHeaderCollection headers, Stream inputStream) { - Contract.Requires(!string.IsNullOrEmpty(httpMethod)); - Contract.Requires(requestUrl != null); - Contract.Requires(rawUrl != null); - Contract.Requires(headers != null); - ErrorUtilities.VerifyNonZeroLength(httpMethod, "httpMethod"); - ErrorUtilities.VerifyArgumentNotNull(requestUrl, "requestUrl"); - ErrorUtilities.VerifyArgumentNotNull(rawUrl, "rawUrl"); - ErrorUtilities.VerifyArgumentNotNull(headers, "headers"); + Contract.RequiresAlways(!string.IsNullOrEmpty(httpMethod)); + Contract.RequiresAlways(requestUrl != null); + Contract.RequiresAlways(rawUrl != null); + Contract.RequiresAlways(headers != null); this.HttpMethod = httpMethod; this.Url = requestUrl; @@ -97,8 +102,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="listenerRequest">Details on the incoming HTTP request.</param> public HttpRequestInfo(HttpListenerRequest listenerRequest) { - Contract.Requires(listenerRequest != null); - ErrorUtilities.VerifyArgumentNotNull(listenerRequest, "listenerRequest"); + Contract.RequiresAlways(listenerRequest != null); this.HttpMethod = listenerRequest.HttpMethod; this.Url = listenerRequest.Url; @@ -117,10 +121,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The WCF incoming request structure to get the HTTP information from.</param> /// <param name="requestUri">The URI of the service endpoint.</param> public HttpRequestInfo(HttpRequestMessageProperty request, Uri requestUri) { - Contract.Requires(request != null); - Contract.Requires(requestUri != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - ErrorUtilities.VerifyArgumentNotNull(requestUri, "requestUri"); + Contract.RequiresAlways(request != null); + Contract.RequiresAlways(requestUri != null); this.HttpMethod = request.Method; this.Headers = request.Headers; @@ -132,6 +134,9 @@ namespace DotNetOpenAuth.Messaging { /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. /// </summary> internal HttpRequestInfo() { + Contract.Ensures(this.HttpMethod == "GET"); + Contract.Ensures(this.Headers != null); + this.HttpMethod = "GET"; this.Headers = new WebHeaderCollection(); } @@ -141,8 +146,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="request">The HttpWebRequest (that was never used) to copy from.</param> internal HttpRequestInfo(WebRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.RequiresAlways(request != null); this.HttpMethod = request.Method; this.Url = request.RequestUri; @@ -194,6 +198,8 @@ namespace DotNetOpenAuth.Messaging { /// </summary> internal Uri UrlBeforeRewriting { get { + Contract.Ensures(Contract.Result<Uri>() != null || this.Url == null || this.RawUrl == null); + if (this.Url == null || this.RawUrl == null) { return null; } @@ -314,6 +320,16 @@ namespace DotNetOpenAuth.Messaging { return query; } +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() { + } +#endif + /// <summary> /// Gets the public facing URL for the given incoming HTTP request. /// </summary> diff --git a/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs b/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs index de1e700..8f169c3 100644 --- a/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs +++ b/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs @@ -6,7 +6,6 @@ namespace DotNetOpenAuth.Messaging { using System; - using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; @@ -189,40 +188,4 @@ namespace DotNetOpenAuth.Messaging { } } } - - [ContractClassFor(typeof(IncomingWebResponse))] - internal abstract class IncomingWebResponseContract : IncomingWebResponse { - public override Stream ResponseStream { - get { throw new NotImplementedException(); } - } - - /// <summary> - /// Creates a text reader for the response stream. - /// </summary> - /// <returns> - /// The text reader, initialized for the proper encoding. - /// </returns> - public override StreamReader GetResponseReader() { - Contract.Ensures(Contract.Result<StreamReader>() != null); - throw new NotImplementedException(); - } - - /// <summary> - /// Gets an offline snapshot version of this instance. - /// </summary> - /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> - /// <returns>A snapshot version of this instance.</returns> - /// <remarks> - /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot - /// will automatically close and dispose of the underlying response stream. - /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will - /// be the self same instance. - /// </remarks> - internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { - Contract.Requires(maximumBytesToCache >= 0); - Contract.Ensures(Contract.Result<CachedDirectWebResponse>() != null); - throw new NotImplementedException(); - } - } - } diff --git a/src/DotNetOpenAuth/Messaging/IncomingWebResponseContract.cs b/src/DotNetOpenAuth/Messaging/IncomingWebResponseContract.cs new file mode 100644 index 0000000..04d3b38 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/IncomingWebResponseContract.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="IncomingWebResponseContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics.Contracts; + using System.IO; + + /// <summary> + /// Code contract for the <see cref="IncomingWebResponse"/> class. + /// </summary> + [ContractClassFor(typeof(IncomingWebResponse))] + internal abstract class IncomingWebResponseContract : IncomingWebResponse { + /// <summary> + /// Gets the body of the HTTP response. + /// </summary> + /// <value></value> + public override Stream ResponseStream { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Creates a text reader for the response stream. + /// </summary> + /// <returns> + /// The text reader, initialized for the proper encoding. + /// </returns> + public override StreamReader GetResponseReader() { + Contract.Ensures(Contract.Result<StreamReader>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets an offline snapshot version of this instance. + /// </summary> + /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> + /// <returns>A snapshot version of this instance.</returns> + /// <remarks> + /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot + /// will automatically close and dispose of the underlying response stream. + /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will + /// be the self same instance. + /// </remarks> + internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { + Contract.Requires(maximumBytesToCache >= 0); + Contract.Requires(this.RequestUri != null); + Contract.Ensures(Contract.Result<CachedDirectWebResponse>() != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs index fb8620b..daf3def 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.3521 +// Runtime Version:2.0.50727.3053 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -304,6 +304,15 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Looks up a localized string similar to A non-empty string was expected.. + /// </summary> + internal static string NonEmptyStringExpected { + get { + return ResourceManager.GetString("NonEmptyStringExpected", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to A message response is already queued for sending in the response stream.. /// </summary> internal static string QueuedMessageResponseAlreadyExists { diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx index 0a6f6a5..7a0ff57 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx +++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx @@ -288,4 +288,7 @@ <data name="UnexpectedHttpStatusCode" xml:space="preserve"> <value>Expected direct response to use HTTP status code {0} but was {1} instead.</value> </data> + <data name="NonEmptyStringExpected" xml:space="preserve"> + <value>A non-empty string was expected.</value> + </data> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index fe1e4e8..f9798f8 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -604,6 +604,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="comparer">A comparison function to compare keys.</param> /// <returns>An System.Linq.IOrderedEnumerable<TElement> whose elements are sorted according to a key.</returns> internal static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Comparison<TKey> comparer) { + Contract.Ensures(Contract.Result<IOrderedEnumerable<TSource>>() != null); return System.Linq.Enumerable.OrderBy<TSource, TKey>(source, keySelector, new ComparisonHelper<TKey>(comparer)); } diff --git a/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs b/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs index 3bbec6e..136b0d0 100644 --- a/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs +++ b/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs @@ -7,10 +7,10 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Diagnostics; + using System.Diagnostics.Contracts; using System.IO; using System.Net; using System.Text; -using System.Diagnostics.Contracts; /// <summary> /// A live network HTTP response @@ -42,6 +42,8 @@ using System.Diagnostics.Contracts; /// <param name="response">The response.</param> internal NetworkDirectWebResponse(Uri requestUri, HttpWebResponse response) : base(requestUri, response) { + Contract.RequiresAlways(requestUri != null); + Contract.RequiresAlways(response != null); this.httpWebResponse = response; this.responseStream = response.GetResponseStream(); } @@ -84,6 +86,7 @@ using System.Diagnostics.Contracts; /// </remarks> internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { ErrorUtilities.VerifyOperation(!this.streamReadBegun, "Network stream reading has already begun."); + this.streamReadBegun = true; var result = new CachedDirectWebResponse(this.RequestUri, this.httpWebResponse, maximumBytesToCache); this.Dispose(); diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs index 5cb7877..60ce7fa 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs @@ -70,6 +70,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// </summary> /// <param name="message">The message the dictionary should provide access to.</param> /// <returns>The dictionary accessor to the message</returns> + [Pure] internal MessageDictionary GetDictionary(IMessage message) { Contract.Requires(message != null); Contract.Ensures(Contract.Result<MessageDictionary>() != null); diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs index 9baa67d..ff93df2 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs @@ -32,6 +32,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <param name="messageType">A type that implements <see cref="IMessage"/>.</param> /// <param name="messageVersion">The protocol version of the message.</param> /// <returns>A <see cref="MessageDescription"/> instance.</returns> + [Pure] internal MessageDescription Get(Type messageType, Version messageVersion) { Contract.Requires(messageType != null && typeof(IMessage).IsAssignableFrom(messageType)); Contract.Requires(messageVersion != null); @@ -50,6 +51,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { } } + Contract.Assume(result != null); // The reflectedMessageTypes dictionary should never have null values. return result; } @@ -61,6 +63,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <returns> /// A <see cref="MessageDescription"/> instance. /// </returns> + [Pure] internal MessageDescription Get(IMessage message) { Contract.Requires(message != null); Contract.Ensures(Contract.Result<MessageDescription>() != null); @@ -72,6 +75,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// </summary> /// <param name="message">The message.</param> /// <returns>The dictionary.</returns> + [Pure] internal MessageDictionary GetAccessor(IMessage message) { Contract.Requires(message != null); ErrorUtilities.VerifyArgumentNotNull(message, "message"); diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs index ec38867..73b62ef 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System.Collections; using System.Collections.Generic; using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; /// <summary> @@ -16,23 +17,24 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// provides access to both well-defined message properties and "extra" /// name/value pairs that have no properties associated with them. /// </summary> - [ContractVerification(true)] + [ContractVerification(false)] internal class MessageDictionary : IDictionary<string, string> { /// <summary> /// The <see cref="IMessage"/> instance manipulated by this dictionary. /// </summary> - private IMessage message; + private readonly IMessage message; /// <summary> /// The <see cref="MessageDescription"/> instance that describes the message type. /// </summary> - private MessageDescription description; + private readonly MessageDescription description; /// <summary> /// Initializes a new instance of the <see cref="MessageDictionary"/> class. /// </summary> /// <param name="message">The message instance whose values will be manipulated by this dictionary.</param> /// <param name="description">The message description.</param> + [Pure] internal MessageDictionary(IMessage message, MessageDescription description) { Contract.Requires(message != null); Contract.Requires(description != null); @@ -201,9 +203,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// Thrown if <paramref name="value"/> is null. /// </exception> public void Add(string key, string value) { - if (value == null) { - throw new ArgumentNullException("value"); - } + ErrorUtilities.VerifyArgumentNotNull(value, "value"); MessagePart part; if (this.description.Mapping.TryGetValue(key, out part)) { @@ -371,6 +371,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// </summary> /// <returns>The generated dictionary.</returns> public IDictionary<string, string> Serialize() { + Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); return this.Serializer.Serialize(this); } @@ -379,7 +380,20 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// </summary> /// <param name="fields">The data to load into the message.</param> public void Deserialize(IDictionary<string, string> fields) { + Contract.Requires(fields != null); this.Serializer.Deserialize(fields, this); } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() { + Contract.Ensures(this.message != null); + Contract.Ensures(this.description != null); + } +#endif } } diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs index f922cc8..8cf86c7 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs @@ -61,12 +61,25 @@ namespace DotNetOpenAuth.Messaging.Reflection { [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "By design.")] [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Much more efficient initialization when we can call methods.")] static MessagePart() { - Map<Uri>(uri => uri.AbsoluteUri, str => new Uri(str)); + Func<string, Uri> safeUri = str => { + Contract.Assume(str != null); + return new Uri(str); + }; + Func<string, bool> safeBool = str => { + Contract.Assume(str != null); + return bool.Parse(str); + }; + Func<string, Identifier> safeIdentfier = str => { + Contract.Assume(str != null); + ErrorUtilities.VerifyFormat(str.Length > 0, MessagingStrings.NonEmptyStringExpected); + return Identifier.Parse(str); + }; + Map<Uri>(uri => uri.AbsoluteUri, safeUri); Map<DateTime>(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), str => XmlConvert.ToDateTime(str, XmlDateTimeSerializationMode.Utc)); Map<byte[]>(bytes => Convert.ToBase64String(bytes), str => Convert.FromBase64String(str)); Map<Realm>(realm => realm.ToString(), str => new Realm(str)); - Map<Identifier>(id => id.ToString(), str => Identifier.Parse(str)); - Map<bool>(value => value.ToString().ToLowerInvariant(), str => bool.Parse(str)); + Map<Identifier>(id => id.ToString(), safeIdentfier); + Map<bool>(value => value.ToString().ToLowerInvariant(), safeBool); } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/Association.cs b/src/DotNetOpenAuth/OpenId/Association.cs index 7514cf9..82890c9 100644 --- a/src/DotNetOpenAuth/OpenId/Association.cs +++ b/src/DotNetOpenAuth/OpenId/Association.cs @@ -33,10 +33,14 @@ namespace DotNetOpenAuth.OpenId { /// <param name="handle">The handle.</param> /// <param name="secret">The secret.</param> /// <param name="totalLifeLength">How long the association will be useful.</param> - /// <param name="issued">When this association was originally issued by the Provider.</param> + /// <param name="issued">The UTC time of when this association was originally issued by the Provider.</param> protected Association(string handle, byte[] secret, TimeSpan totalLifeLength, DateTime issued) { Contract.RequiresAlways(!string.IsNullOrEmpty(handle)); Contract.RequiresAlways(secret != null); + Contract.RequiresAlways(totalLifeLength > TimeSpan.Zero); + Contract.RequiresAlways(issued.Kind == DateTimeKind.Utc); + Contract.RequiresAlways(issued <= DateTime.UtcNow); + this.Handle = handle; this.SecretKey = secret; this.TotalLifeLength = totalLifeLength; @@ -190,7 +194,9 @@ namespace DotNetOpenAuth.OpenId { // re-instantiated on deserialization. // For now, we just send out the secret key. We can derive the type from the length later. byte[] secretKeyCopy = new byte[this.SecretKey.Length]; - this.SecretKey.CopyTo(secretKeyCopy, 0); + if (this.SecretKey.Length > 0) { + this.SecretKey.CopyTo(secretKeyCopy, 0); + } return secretKeyCopy; } @@ -275,54 +281,17 @@ namespace DotNetOpenAuth.OpenId { /// </summary> /// <returns>The hash algorithm used for message signing.</returns> protected abstract HashAlgorithm CreateHasher(); - } - - /// <summary> - /// Code contract for the <see cref="Association"/> class. - /// </summary> - [ContractClassFor(typeof(Association))] - internal abstract class AssociationContract : Association { - /// <summary> - /// Initializes a new instance of the <see cref="AssociationContract"/> class. - /// </summary> - private AssociationContract() - : base(null, null, TimeSpan.Zero, DateTime.Now) { - } - - /// <summary> - /// Gets the length (in bits) of the hash this association creates when signing. - /// </summary> - public override int HashBitLength { - get { - Contract.Ensures(Contract.Result<int>() > 0); - throw new NotImplementedException(); - } - } - - /// <summary> - /// The string to pass as the assoc_type value in the OpenID protocol. - /// </summary> - /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param> - /// <returns> - /// The value that should be used for the openid.assoc_type parameter. - /// </returns> - [Pure] - internal override string GetAssociationType(Protocol protocol) { - Contract.Requires(protocol != null); - throw new NotImplementedException(); - } +#if CONTRACTS_FULL /// <summary> - /// Returns the specific hash algorithm used for message signing. + /// Verifies conditions that should be true for any valid state of this object. /// </summary> - /// <returns> - /// The hash algorithm used for message signing. - /// </returns> - [Pure] - protected override HashAlgorithm CreateHasher() { - Contract.Ensures(Contract.Result<HashAlgorithm>() != null); - throw new NotImplementedException(); + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() { + Contract.Invariant(!string.IsNullOrEmpty(this.Handle)); + Contract.Invariant(this.TotalLifeLength > TimeSpan.Zero); } +#endif } - } diff --git a/src/DotNetOpenAuth/OpenId/AssociationContract.cs b/src/DotNetOpenAuth/OpenId/AssociationContract.cs new file mode 100644 index 0000000..7a8398e --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/AssociationContract.cs @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociationContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.IO; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Code contract for the <see cref="Association"/> class. + /// </summary> + [ContractClassFor(typeof(Association))] + internal abstract class AssociationContract : Association { + /// <summary> + /// Prevents a default instance of the <see cref="AssociationContract"/> class from being created. + /// </summary> + private AssociationContract() + : base(null, null, TimeSpan.Zero, DateTime.Now) { + } + + /// <summary> + /// Gets the length (in bits) of the hash this association creates when signing. + /// </summary> + public override int HashBitLength { + get { + Contract.Ensures(Contract.Result<int>() > 0); + throw new NotImplementedException(); + } + } + + /// <summary> + /// The string to pass as the assoc_type value in the OpenID protocol. + /// </summary> + /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param> + /// <returns> + /// The value that should be used for the openid.assoc_type parameter. + /// </returns> + [Pure] + internal override string GetAssociationType(Protocol protocol) { + Contract.Requires(protocol != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Returns the specific hash algorithm used for message signing. + /// </summary> + /// <returns> + /// The hash algorithm used for message signing. + /// </returns> + [Pure] + protected override HashAlgorithm CreateHasher() { + Contract.Ensures(Contract.Result<HashAlgorithm>() != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Associations.cs b/src/DotNetOpenAuth/OpenId/Associations.cs index 5386ac9..9f8c1d4 100644 --- a/src/DotNetOpenAuth/OpenId/Associations.cs +++ b/src/DotNetOpenAuth/OpenId/Associations.cs @@ -5,13 +5,13 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId { - using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Linq; using DotNetOpenAuth.Messaging; -using System.Diagnostics.Contracts; /// <summary> /// A dictionary of handle/Association pairs. @@ -109,5 +109,16 @@ using System.Diagnostics.Contracts; } } } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() { + Contract.Invariant(this.associations != null); + } +#endif } } diff --git a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs index df1dede..cbba342 100644 --- a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs +++ b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs @@ -67,9 +67,9 @@ namespace DotNetOpenAuth.OpenId { /// <param name="totalLifeLength">The time duration the association will be good for.</param> private HmacShaAssociation(HmacSha typeIdentity, string handle, byte[] secret, TimeSpan totalLifeLength) : base(handle, secret, totalLifeLength, DateTime.UtcNow) { - ErrorUtilities.VerifyArgumentNotNull(typeIdentity, "typeIdentity"); - ErrorUtilities.VerifyNonZeroLength(handle, "handle"); - ErrorUtilities.VerifyArgumentNotNull(secret, "secret"); + Contract.Requires(typeIdentity != null); + Contract.Requires(!String.IsNullOrEmpty(handle)); + Contract.Requires(secret != null); ErrorUtilities.VerifyProtocol(secret.Length == typeIdentity.SecretLength, OpenIdStrings.AssociationSecretAndTypeLengthMismatch, secret.Length, typeIdentity.GetAssociationType(Protocol.Default)); this.typeIdentity = typeIdentity; @@ -171,6 +171,9 @@ namespace DotNetOpenAuth.OpenId { lifetime = DumbSecretLifetime; } + Contract.Assert(protocol != null); // All the way up to the method call, the condition holds, yet we get a Requires failure next + Contract.Assert(secret != null); + Contract.Assert(!String.IsNullOrEmpty(associationType)); return Create(protocol, associationType, handle, secret, lifetime); } @@ -265,7 +268,9 @@ namespace DotNetOpenAuth.OpenId { /// </returns> [Pure] protected override HashAlgorithm CreateHasher() { - return this.typeIdentity.CreateHasher(SecretKey); + var result = this.typeIdentity.CreateHasher(SecretKey); + Contract.Assume(result != null); + return result; } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/IAssociationStore.cs b/src/DotNetOpenAuth/OpenId/IAssociationStore.cs index 4939cec..bda64cf 100644 --- a/src/DotNetOpenAuth/OpenId/IAssociationStore.cs +++ b/src/DotNetOpenAuth/OpenId/IAssociationStore.cs @@ -4,9 +4,10 @@ // </copyright> //----------------------------------------------------------------------- -using System.Diagnostics.Contracts; -using System; namespace DotNetOpenAuth.OpenId { + using System; + using System.Diagnostics.Contracts; + /// <summary> /// An enumeration that can specify how a given <see cref="Association"/> is used. /// </summary> @@ -96,39 +97,96 @@ namespace DotNetOpenAuth.OpenId { void ClearExpiredAssociations(); } + /// <summary> + /// Code Contract for the <see cref="IAssociationStore<TKey>"/> class. + /// </summary> + /// <typeparam name="TKey">The type of the key.</typeparam> [ContractClassFor(typeof(IAssociationStore<>))] internal class IAssociationStoreContract<TKey> : IAssociationStore<TKey> { #region IAssociationStore<TKey> Members + /// <summary> + /// Saves an <see cref="Association"/> for later recall. + /// </summary> + /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for providers).</param> + /// <param name="association">The association to store.</param> + /// <remarks> + /// TODO: what should implementations do on association handle conflict? + /// </remarks> void IAssociationStore<TKey>.StoreAssociation(TKey distinguishingFactor, Association association) { Contract.Requires(distinguishingFactor != null); Contract.Requires(association != null); throw new NotImplementedException(); } + /// <summary> + /// Gets the best association (the one with the longest remaining life) for a given key. + /// </summary> + /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. + /// </returns> + /// <remarks> + /// In the event that multiple associations exist for the given + /// <paramref name="distinguishingFactor"/>, it is important for the + /// implementation for this method to use the <paramref name="securityRequirements"/> + /// to pick the best (highest grade or longest living as the host's policy may dictate) + /// association that fits the security requirements. + /// Associations that are returned that do not meet the security requirements will be + /// ignored and a new association created. + /// </remarks> Association IAssociationStore<TKey>.GetAssociation(TKey distinguishingFactor, SecuritySettings securityRequirements) { Contract.Requires(distinguishingFactor != null); Contract.Requires(securityRequirements != null); throw new NotImplementedException(); } + /// <summary> + /// Gets the association for a given key and handle. + /// </summary> + /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="handle">The handle of the specific association that must be recalled.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. + /// </returns> Association IAssociationStore<TKey>.GetAssociation(TKey distinguishingFactor, string handle) { Contract.Requires(distinguishingFactor != null); Contract.Requires(!String.IsNullOrEmpty(handle)); throw new NotImplementedException(); } + /// <summary> + /// Removes a specified handle that may exist in the store. + /// </summary> + /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="handle">The handle of the specific association that must be deleted.</param> + /// <returns> + /// True if the association existed in this store previous to this call. + /// </returns> + /// <remarks> + /// No exception should be thrown if the association does not exist in the store + /// before this call. + /// </remarks> bool IAssociationStore<TKey>.RemoveAssociation(TKey distinguishingFactor, string handle) { Contract.Requires(distinguishingFactor != null); Contract.Requires(!String.IsNullOrEmpty(handle)); throw new NotImplementedException(); } + /// <summary> + /// Clears all expired associations from the store. + /// </summary> + /// <remarks> + /// If another algorithm is in place to periodically clear out expired associations, + /// this method call may be ignored. + /// This should be done frequently enough to avoid a memory leak, but sparingly enough + /// to not be a performance drain. + /// </remarks> void IAssociationStore<TKey>.ClearExpiredAssociations() { throw new NotImplementedException(); } #endregion } - } diff --git a/src/DotNetOpenAuth/OpenId/Protocol.cs b/src/DotNetOpenAuth/OpenId/Protocol.cs index b9f2cca..f38d8a0 100644 --- a/src/DotNetOpenAuth/OpenId/Protocol.cs +++ b/src/DotNetOpenAuth/OpenId/Protocol.cs @@ -11,6 +11,7 @@ namespace DotNetOpenAuth.OpenId { using DotNetOpenAuth.Messaging; using System.Globalization; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; /// <summary> /// An enumeration of the OpenID protocol versions supported by this library. @@ -34,7 +35,7 @@ namespace DotNetOpenAuth.OpenId { /// Tracks the several versions of OpenID this library supports and the unique /// constants to each version used in the protocol. /// </summary> - internal class Protocol { + internal sealed class Protocol { /// <summary> /// The value of the openid.ns parameter in the OpenID 2.0 specification. /// </summary> @@ -251,7 +252,7 @@ namespace DotNetOpenAuth.OpenId { /// </summary> public QueryArguments Args = new QueryArguments(); - internal class QueryParameters { + internal sealed class QueryParameters { /// <summary> /// The value "openid." /// </summary> @@ -317,18 +318,29 @@ namespace DotNetOpenAuth.OpenId { public string dh_server_public = "dh_server_public"; public string enc_mac_key = "enc_mac_key"; public string mac_key = "mac_key"; + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(!string.IsNullOrEmpty(this.Prefix)); + } +#endif } - internal class QueryArguments { + internal sealed class QueryArguments { public ErrorCodes ErrorCode = new ErrorCodes(); public SessionTypes SessionType = new SessionTypes(); public SignatureAlgorithms SignatureAlgorithm = new SignatureAlgorithms(); public Modes Mode = new Modes(); public IsValidValues IsValid = new IsValidValues(); - internal class ErrorCodes { + internal sealed class ErrorCodes { public string UnsupportedType = "unsupported-type"; } - internal class SessionTypes { + internal sealed class SessionTypes { /// <summary> /// A preference order list of all supported session types. /// </summary> @@ -350,7 +362,7 @@ namespace DotNetOpenAuth.OpenId { } } } - internal class SignatureAlgorithms { + internal sealed class SignatureAlgorithms { /// <summary> /// A preference order list of signature algorithms we support. /// </summary> @@ -370,7 +382,7 @@ namespace DotNetOpenAuth.OpenId { } } } - internal class Modes { + internal sealed class Modes { public string cancel = "cancel"; public string error = "error"; public string id_res = "id_res"; @@ -380,7 +392,7 @@ namespace DotNetOpenAuth.OpenId { public string associate = "associate"; public string setup_needed = "id_res"; // V2 overrides this } - internal class IsValidValues { + internal sealed class IsValidValues { public string True = "true"; public string False = "false"; } diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs index c3780a9..44ad111 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs @@ -309,16 +309,12 @@ namespace DotNetOpenAuth.OpenId.Provider { /// the user agent to allow the redirect with assertion to happen. /// </returns> public OutgoingWebResponse PrepareUnsolicitedAssertion(Uri providerEndpoint, Realm relyingParty, Identifier claimedIdentifier, Identifier localIdentifier, params IExtensionMessage[] extensions) { - Contract.Requires(providerEndpoint != null); - Contract.Requires(providerEndpoint.IsAbsoluteUri); - Contract.Requires(relyingParty != null); - Contract.Requires(claimedIdentifier != null); - Contract.Requires(localIdentifier != null); - ErrorUtilities.VerifyArgumentNotNull(providerEndpoint, "providerEndpoint"); - ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); - ErrorUtilities.VerifyArgumentNotNull(claimedIdentifier, "claimedIdentifier"); - ErrorUtilities.VerifyArgumentNotNull(localIdentifier, "localIdentifier"); - ErrorUtilities.VerifyArgumentNamed(providerEndpoint.IsAbsoluteUri, "providerEndpoint", OpenIdStrings.AbsoluteUriRequired); + Contract.RequiresAlways(providerEndpoint != null); + Contract.RequiresAlways(providerEndpoint.IsAbsoluteUri); + Contract.RequiresAlways(relyingParty != null); + Contract.RequiresAlways(claimedIdentifier != null); + Contract.RequiresAlways(localIdentifier != null); + Contract.RequiresAlways(this.WebRequestHandler != null); // Although the RP should do their due diligence to make sure that this OP // is authorized to send an assertion for the given claimed identifier, diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs index 2e22aec..9c24635 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs @@ -237,11 +237,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { Contract.Requires(userSuppliedIdentifier != null); Contract.Requires(relyingParty != null); Contract.Requires(realm != null); - - // We have a long data validation and preparation process - ErrorUtilities.VerifyArgumentNotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); - ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); - ErrorUtilities.VerifyArgumentNotNull(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 diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index 3a4a92a..b665a4b 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -18,6 +18,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using DotNetOpenAuth.OpenId.ChannelElements; using DotNetOpenAuth.OpenId.Extensions; using DotNetOpenAuth.OpenId.Messages; + using System.Diagnostics.CodeAnalysis; /// <summary> /// A delegate that decides whether a given OpenID Provider endpoint may be @@ -162,7 +163,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { internal set { Contract.Requires(value != null); - ErrorUtilities.VerifyArgumentNotNull(value, "value"); this.securitySettings = value; this.AssociationManager.SecuritySettings = value; } @@ -195,8 +195,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } set { - Contract.Requires(value != null); - ErrorUtilities.VerifyArgumentNotNull(value, "value"); + Contract.RequiresAlways(value != null); this.endpointOrder = value; } } @@ -464,6 +463,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { Contract.Requires(userSuppliedIdentifier != null); Contract.Requires(realm != null); Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null); + Contract.Ensures(Contract.ForAll(Contract.Result<IEnumerable<IAuthenticationRequest>>(), el => el != null)); + ErrorUtilities.VerifyHttpContext(); // Build the return_to URL @@ -526,6 +527,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { return this.CreateRequests(userSuppliedIdentifier, new Realm(realmUrl.Uri)); } +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.securitySettings != null); + Contract.Invariant(this.channel != null); + Contract.Invariant(this.endpointOrder != null); + } +#endif + /// <summary> /// Releases unmanaged and - optionally - managed resources /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs index 1bb03ee..6d491a2 100644 --- a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs @@ -156,7 +156,6 @@ namespace DotNetOpenAuth.OpenId { /// </returns> internal static bool IsValidXri(string xri) { Contract.Requires((xri != null && xri.Length > 0) || !string.IsNullOrEmpty(xri)); - ErrorUtilities.VerifyNonZeroLength(xri, "xri"); xri = xri.Trim(); // TODO: better validation code here |