summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Set-ProjectName.ps14
-rw-r--r--build.proj4
-rw-r--r--doc/specs/OAuth Core 1.0.htm2438
-rw-r--r--doc/specs/OAuth Core 1.0_files/diagram.pngbin0 -> 107686 bytes
-rw-r--r--lib/System.Web.Abstractions.dllbin0 -> 87032 bytes
-rw-r--r--lib/System.Web.Mvc.dllbin0 -> 113144 bytes
-rw-r--r--lib/System.Web.Routing.dllbin0 -> 70648 bytes
-rw-r--r--readme.txt8
-rw-r--r--src/DotNetOAuth.Test/.gitignore (renamed from src/YOURLIBNAME.Test/.gitignore)0
-rw-r--r--src/DotNetOAuth.Test/DotNetOAuth.Test.csproj (renamed from src/YOURLIBNAME.Test/YOURLIBNAME.Test.csproj)22
-rw-r--r--src/DotNetOAuth.Test/Logging.config (renamed from src/YOURLIBNAME.Test/Logging.config)0
-rw-r--r--src/DotNetOAuth.Test/Messaging/ChannelTests.cs107
-rw-r--r--src/DotNetOAuth.Test/Messaging/DictionaryXmlReaderTests.cs26
-rw-r--r--src/DotNetOAuth.Test/Messaging/MessageSerializerTests.cs142
-rw-r--r--src/DotNetOAuth.Test/Messaging/ResponseTest.cs19
-rw-r--r--src/DotNetOAuth.Test/MessagingUtilitiesTest.cs99
-rw-r--r--src/DotNetOAuth.Test/Mocks/TestBadChannel.cs42
-rw-r--r--src/DotNetOAuth.Test/Mocks/TestChannel.cs39
-rw-r--r--src/DotNetOAuth.Test/Mocks/TestDirectedMessage.cs47
-rw-r--r--src/DotNetOAuth.Test/Mocks/TestMessage.cs41
-rw-r--r--src/DotNetOAuth.Test/Mocks/TestMessageTypeProvider.cs31
-rw-r--r--src/DotNetOAuth.Test/OAuthChannelTests.cs49
-rw-r--r--src/DotNetOAuth.Test/Properties/AssemblyInfo.cs (renamed from src/YOURLIBNAME.Test/Properties/AssemblyInfo.cs)5
-rw-r--r--src/DotNetOAuth.Test/ServiceProviderTest.cs65
-rw-r--r--src/DotNetOAuth.Test/Settings.StyleCop24
-rw-r--r--src/DotNetOAuth.Test/TestBase.cs (renamed from src/YOURLIBNAME.Test/TestBase.cs)6
-rw-r--r--src/DotNetOAuth.sln (renamed from src/YOURLIBNAME.sln)8
-rw-r--r--src/DotNetOAuth.vsmdi (renamed from src/YOURLIBNAME.vsmdi)0
-rw-r--r--src/DotNetOAuth/.gitignore (renamed from src/YOURLIBNAME/.gitignore)1
-rw-r--r--src/DotNetOAuth/ClassDiagram.cd74
-rw-r--r--src/DotNetOAuth/Consumer.cs13
-rw-r--r--src/DotNetOAuth/DotNetOAuth.csproj (renamed from src/YOURLIBNAME/YOURLIBNAME.csproj)55
-rw-r--r--src/DotNetOAuth/Logger.cs (renamed from src/YOURLIBNAME/Logger.cs)8
-rw-r--r--src/DotNetOAuth/Loggers/ILog.cs (renamed from src/YOURLIBNAME/Loggers/ILog.cs)2
-rw-r--r--src/DotNetOAuth/Loggers/Log4NetLogger.cs (renamed from src/YOURLIBNAME/Loggers/Log4NetLogger.cs)2
-rw-r--r--src/DotNetOAuth/Loggers/NoOpLogger.cs (renamed from src/YOURLIBNAME/Loggers/NoOpLogger.cs)2
-rw-r--r--src/DotNetOAuth/Loggers/TraceLogger.cs (renamed from src/YOURLIBNAME/Loggers/TraceLogger.cs)2
-rw-r--r--src/DotNetOAuth/Messaging/Channel.cs354
-rw-r--r--src/DotNetOAuth/Messaging/DictionaryXmlReader.cs91
-rw-r--r--src/DotNetOAuth/Messaging/DictionaryXmlWriter.cs273
-rw-r--r--src/DotNetOAuth/Messaging/HttpRequestInfo.cs137
-rw-r--r--src/DotNetOAuth/Messaging/IDirectedProtocolMessage.cs20
-rw-r--r--src/DotNetOAuth/Messaging/IMessageTypeProvider.cs41
-rw-r--r--src/DotNetOAuth/Messaging/IProtocolMessage.cs42
-rw-r--r--src/DotNetOAuth/Messaging/MessageScheme.cs35
-rw-r--r--src/DotNetOAuth/Messaging/MessageSerializer.cs167
-rw-r--r--src/DotNetOAuth/Messaging/MessageTransport.cs22
-rw-r--r--src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs108
-rw-r--r--src/DotNetOAuth/Messaging/MessagingStrings.resx135
-rw-r--r--src/DotNetOAuth/Messaging/MessagingUtilities.cs119
-rw-r--r--src/DotNetOAuth/Messaging/Response.cs73
-rw-r--r--src/DotNetOAuth/OAuthChannel.cs246
-rw-r--r--src/DotNetOAuth/OAuthMessageTypeProvider.cs52
-rw-r--r--src/DotNetOAuth/Properties/AssemblyInfo.cs (renamed from src/YOURLIBNAME/Properties/AssemblyInfo.cs)10
-rw-r--r--src/DotNetOAuth/Protocol.cs75
-rw-r--r--src/DotNetOAuth/ProtocolException.cs45
-rw-r--r--src/DotNetOAuth/ServiceProvider.cs63
-rw-r--r--src/DotNetOAuth/Settings.StyleCop (renamed from src/YOURLIBNAME/Settings.StyleCop)0
-rw-r--r--src/DotNetOAuth/Strings.Designer.cs90
-rw-r--r--src/DotNetOAuth/Strings.resx129
-rw-r--r--src/DotNetOAuth/UriUtil.cs31
-rw-r--r--src/DotNetOAuth/Util.cs (renamed from src/YOURLIBNAME/Util.cs)2
-rw-r--r--src/LocalTestRun.testrunconfig2
-rw-r--r--src/Settings.StyleCop25
-rw-r--r--src/YOURLIBNAME.Test/Settings.StyleCop1
-rw-r--r--tools/Documentation.targets2
-rw-r--r--tools/DotNetOAuth.Common.Settings.targets (renamed from tools/YOURLIBNAME.Common.Settings.targets)5
-rw-r--r--tools/DotNetOAuth.Versioning.targets (renamed from tools/YOURLIBNAME.Versioning.targets)4
-rw-r--r--tools/Sandcastle/Presentation/vs2005/Content/feedBack_content.xml4
-rw-r--r--tools/Sandcastle/Presentation/vs2005/Content/reference_content.xml2
-rw-r--r--tools/Sandcastle/Presentation/vs2005/Content/shared_content.xml2
-rw-r--r--tools/libcheck.ps16
-rw-r--r--tools/sandcastle.targets4
73 files changed, 5738 insertions, 64 deletions
diff --git a/Set-ProjectName.ps1 b/Set-ProjectName.ps1
deleted file mode 100644
index 6a49d7f..0000000
--- a/Set-ProjectName.ps1
+++ /dev/null
@@ -1,4 +0,0 @@
-param ($libraryName = { throw "-libraryName required" } )
-
-dir -rec . *YOURLIBNAME* |% { ren $_.FullName $_.Name.Replace("YOURLIBNAME", $libraryName) -whatif }
-
diff --git a/build.proj b/build.proj
index 64e1a3f..4db4d06 100644
--- a/build.proj
+++ b/build.proj
@@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$(MSBuildProjectDirectory)\tools\YOURLIBNAME.Common.Settings.targets"/>
+ <Import Project="$(MSBuildProjectDirectory)\tools\DotNetOAuth.Common.Settings.targets"/>
<PropertyGroup>
<AutomatedBuild>true</AutomatedBuild>
</PropertyGroup>
@@ -82,7 +82,7 @@
</Target>
<Target Name="_EnsureCleanDrop">
- <!-- This target only does a clean sufficient to guarantee that our YOURLIBNAME.dll is rebuilt, but
+ <!-- This target only does a clean sufficient to guarantee that our DotNetOAuth.dll is rebuilt, but
we don't usually want to clean our documentation because that takes forever to build froms scratch. -->
<MSBuild Projects="$(ProjectRoot)\src\$(ProductName).sln" Targets="Clean" />
</Target>
diff --git a/doc/specs/OAuth Core 1.0.htm b/doc/specs/OAuth Core 1.0.htm
new file mode 100644
index 0000000..6a9857e
--- /dev/null
+++ b/doc/specs/OAuth Core 1.0.htm
@@ -0,0 +1,2438 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head>
+<title>OAuth Core 1.0</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<meta name="description" content="OAuth Core 1.0">
+<style type="text/css">
+ body {
+ font-family: "lucida grande", verdana, arial, helvetica, sans-serif;
+ font-size: 83%; color: #000; background-color: #FFF;
+ margin: 2em;
+ }
+ h1, h2, h3, h4, h5, h6 {
+ font-family: helvetica neue, arial, "MS Sans Serif", sans-serif;
+ font-weight: bold; font-style: normal;
+ }
+ h1 { color: #111; background-color: transparent; padding-bottom: 2px; border-bottom: 4px solid #efefef; }
+ h3 { color: #333; background-color: transparent; }
+
+
+ td.header {
+ font-family: arial, helvetica, sans-serif; font-size: x-small;
+ vertical-align: top; width: 33%;
+ color: #FFF; background-color: #666;
+ }
+ td.author { font-weight: bold; font-size: x-small; margin-left: 4em; }
+ td.author-text { font-size: x-small; }
+
+ /* info code from SantaKlauss at http://www.madaboutstyle.com/tooltip2.html */
+ a.info {
+ /* This is the key. */
+ position: relative;
+ z-index: 24;
+ }
+ a.info:hover {
+ z-index: 25;
+ color: #FFF; background-color: #369;
+ text-decoration: none;
+ }
+ a.info span { display: none; }
+ a.info:hover span.info {
+ /* The span will display just on :hover state. */
+ display: block;
+ position: absolute;
+ font-size: smaller;
+ top: 2em; left: -5em; width: 15em;
+ padding: 2px; border: 1px solid #333;
+ color: #369; background-color: #EEE;
+ text-align: left;
+ }
+
+ a { font-weight: bold; }
+ a:link { color: #369; background-color: transparent; }
+ a:visited { color: #666; background-color: transparent; }
+ a:active { color: #888; background-color: transparent; }
+
+ p { margin-left: 2em; margin-right: 2em; }
+ p.copyright { font-size: x-small; }
+ p.toc { font-size: small; font-weight: bold; margin-left: 3em; }
+ table.toc { margin: 0 0 0 3em; padding: 0; border: 0; vertical-align: text-top; }
+ td.toc { font-size: small; font-weight: bold; vertical-align: text-top; }
+
+ ol.text { margin-left: 1em; margin-right: 2em; }
+ ul.text { margin-left: 1em; margin-right: 2em; }
+ li { margin-left: 1em; padding-left: 1.2em; }
+
+ /* RFC-2629 <spanx>s and <artwork>s. */
+ em { font-style: italic; }
+ strong { font-weight: bold; }
+ dfn { font-weight: bold; font-style: normal; }
+ cite { font-weight: normal; font-style: normal; }
+ tt { color: #036; }
+ tt, pre, pre dfn, pre em, pre cite, pre span {
+ font-family: "Courier New", Courier, monospace; font-size: small;
+ }
+ pre {
+ text-align: left; padding: 4px;
+ color: #000; background-color: #f7f7f7;
+ width: auto;
+ }
+ pre dfn { color: #369; }
+ pre em { color: #66F; background-color: #FFC; font-weight: normal; }
+ pre .key { color: #33C; font-weight: bold; }
+ pre .id { color: #369; }
+ pre .str { color: #000; background-color: #CFF; }
+ pre .val { color: #066; }
+ pre .rep { color: #909; }
+ pre .oth { color: #000; background-color: #FCF; }
+ pre .err { background-color: #FCC; }
+
+ /* RFC-2629 <texttable>s. */
+ table.all, table.full, table.headers, table.none {
+ font-size: small; text-align: center; border-width: 2px;
+ vertical-align: top; border-collapse: collapse;
+ }
+ table.all, table.full { border-style: solid; border-color: black; }
+ table.headers, table.none { border-style: none; }
+ th {
+ font-weight: bold; border-color: #000;
+ border-width: 2px 2px 3px 2px;
+ }
+ table.all th, table.full th { border-style: solid; }
+ table.headers th { border-style: none none solid none; }
+ table.none th { border-style: none; }
+ table.all td {
+ border-style: solid; border-color: #333;
+ border-width: 1px 2px;
+ }
+ table.full td, table.headers td, table.none td { border-style: none; }
+
+ hr { height: 1px; border:1px solid #e7e7e7; background-color:#e7e7e7;}
+ hr.insert {
+ width: 80%; border-style: none; border-width: 0;
+ color: #CCC; background-color: #CCC;
+ }
+</style>
+</head><body>
+<p><span style="float: right;">December 4, 2007</span></p>
+
+<h1><br>OAuth Core 1.0</h1>
+
+<h3>Abstract</h3>
+
+<p>
+ The OAuth protocol enables websites or applications (Consumers) to
+ access Protected Resources from a web service (Service Provider) via an
+ API, without requiring Users to disclose their Service Provider
+ credentials to the Consumers. More generally, OAuth creates a
+ freely-implementable and generic methodology for API authentication.
+
+</p>
+
+<p>
+ An example use case is allowing printing service printer.example.com
+ (the Consumer), to access private photos stored on photos.example.net
+ (the Service Provider) without requiring Users to provide their
+ photos.example.net credentials to printer.example.com.
+
+</p>
+
+<p>
+ OAuth does not require a specific user interface or interaction
+ pattern, nor does it specify how Service Providers authenticate Users,
+ making the protocol ideally suited for cases where authentication
+ credentials are unavailable to the Consumer, such as with OpenID.
+
+</p>
+
+<p>
+ OAuth aims to unify the experience and implementation of delegated web
+ service authentication into a single, community-driven protocol. OAuth
+ builds on existing protocols and best practices that have been
+ independently implemented by various websites. An open standard,
+ supported by large and small providers alike, promotes a consistent and
+ trusted experience for both application developers and the users of
+ those applications.
+
+</p><a name="toc"></a><br><hr>
+<h3>Table of Contents</h3>
+<p class="toc">
+<a href="#anchor1">1.</a>&nbsp;
+Authors<br>
+<a href="#anchor2">2.</a>&nbsp;
+Notation and Conventions<br>
+<a href="#anchor3">3.</a>&nbsp;
+Definitions<br>
+<a href="#anchor4">4.</a>&nbsp;
+Documentation and Registration<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#request_urls">4.1.</a>&nbsp;
+Request URLs<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#anchor5">4.2.</a>&nbsp;
+Service Providers<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#anchor6">4.3.</a>&nbsp;
+Consumers<br>
+<a href="#anchor7">5.</a>&nbsp;
+Parameters<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#encoding_parameters">5.1.</a>&nbsp;
+Parameter Encoding<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#consumer_req_param">5.2.</a>&nbsp;
+Consumer Request Parameters<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#response_parameters">5.3.</a>&nbsp;
+Service Provider Response Parameters<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#auth_header">5.4.</a>&nbsp;
+OAuth HTTP Authorization Scheme<br>
+<a href="#anchor9">6.</a>&nbsp;
+Authenticating with OAuth<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#auth_step1">6.1.</a>&nbsp;
+Obtaining an Unauthorized Request Token<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#auth_step2">6.2.</a>&nbsp;
+Obtaining User Authorization<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#auth_step3">6.3.</a>&nbsp;
+Obtaining an Access Token<br>
+<a href="#anchor13">7.</a>&nbsp;
+Accessing Protected Resources<br>
+<a href="#nonce">8.</a>&nbsp;
+Nonce and Timestamp<br>
+<a href="#signing_process">9.</a>&nbsp;
+Signing Requests<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#anchor14">9.1.</a>&nbsp;
+Signature Base String<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#anchor16">9.2.</a>&nbsp;
+HMAC-SHA1<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#anchor19">9.3.</a>&nbsp;
+RSA-SHA1<br>
+&nbsp;&nbsp;&nbsp;&nbsp;<a href="#anchor22">9.4.</a>&nbsp;
+PLAINTEXT<br>
+<a href="#http_codes">10.</a>&nbsp;
+HTTP Response Codes<br>
+<a href="#anchor25">Appendix&nbsp;A.</a>&nbsp;
+Appendix A - Protocol Example<br>
+<a href="#anchor26">Appendix&nbsp;A.1.</a>&nbsp;
+Documentation and Registration<br>
+<a href="#anchor27">Appendix&nbsp;A.2.</a>&nbsp;
+Obtaining a Request Token<br>
+<a href="#anchor28">Appendix&nbsp;A.3.</a>&nbsp;
+Requesting User Authorization<br>
+<a href="#anchor29">Appendix&nbsp;A.4.</a>&nbsp;
+Obtaining an Access Token<br>
+<a href="#anchor30">Appendix&nbsp;A.5.</a>&nbsp;
+Accessing Protected Resources<br>
+<a href="#anchor33">Appendix&nbsp;B.</a>&nbsp;
+Security Considerations<br>
+<a href="#anchor34">Appendix&nbsp;B.1.</a>&nbsp;
+Credentials and Token Exchange<br>
+<a href="#anchor35">Appendix&nbsp;B.2.</a>&nbsp;
+PLAINTEXT Signature Method<br>
+<a href="#anchor36">Appendix&nbsp;B.3.</a>&nbsp;
+Confidentiality of Requests<br>
+<a href="#anchor37">Appendix&nbsp;B.4.</a>&nbsp;
+Spoofing by Counterfeit Servers<br>
+<a href="#anchor38">Appendix&nbsp;B.5.</a>&nbsp;
+Proxying and Caching of Authenticated Content<br>
+<a href="#anchor39">Appendix&nbsp;B.6.</a>&nbsp;
+Plaintext Storage of Credentials<br>
+<a href="#anchor40">Appendix&nbsp;B.7.</a>&nbsp;
+Secrecy of the Consumer Secret<br>
+<a href="#anchor41">Appendix&nbsp;B.8.</a>&nbsp;
+Phishing Attacks<br>
+<a href="#anchor42">Appendix&nbsp;B.9.</a>&nbsp;
+Scoping of Access Requests<br>
+<a href="#anchor43">Appendix&nbsp;B.10.</a>&nbsp;
+Entropy of Secrets<br>
+<a href="#anchor44">Appendix&nbsp;B.11.</a>&nbsp;
+Denial of Service / Resource Exhaustion Attacks<br>
+<a href="#anchor45">Appendix&nbsp;B.12.</a>&nbsp;
+Cryptographic Attacks<br>
+<a href="#anchor46">Appendix&nbsp;B.13.</a>&nbsp;
+Signature Base String Compatibility<br>
+<a href="#rfc.references1">11.</a>&nbsp;
+References<br>
+<a href="#rfc.authors">§</a>&nbsp;
+Author’s Address<br>
+</p>
+
+<p><br clear="all"></p>
+
+<p><a name="anchor1"></a><br></p><hr>
+
+<p><a name="rfc.section.1"></a></p><h3>1.&nbsp;
+Authors</h3>
+
+<ul>
+ <li><span class="vcard"><span class="fn">Mark Atwood</span> (<span class="email">me@mark.atwood.name</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Richard M. Conlan</span> (<span class="email">zeveck@google.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Blaine Cook</span> (<span class="email">blaine@twitter.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Leah Culver</span> (<span class="email">leah@pownce.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Kellan Elliott-McCrea</span> (<span class="email">kellan@flickr.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Larry Halff</span> (<span class="email">larry@ma.gnolia.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Eran Hammer-Lahav</span> (<span class="email">eran@hueniverse.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Ben Laurie</span> (<span class="email">benl@google.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Chris Messina</span> (<span class="email">chris@citizenagency.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">John Panzer</span> (<span class="email">jpanzer@acm.org</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Sam Quigley</span> (<span class="email">quigley@emerose.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">David Recordon</span> (<span class="email">david@sixapart.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Eran Sandler</span> (<span class="email">eran@yedda.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Jonathan Sergent</span> (<span class="email">sergent@google.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Todd Sieling</span> (<span class="email">todd@ma.gnolia.com</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Brian Slesinsky</span> (<span class="email">brian-oauth@slesinsky.org</span>)</span></li>
+ <li><span class="vcard"><span class="fn">Andy Smith</span> (<span class="email">andy@jaiku.com</span>)</span></li>
+</ul>
+
+<p><a name="anchor2"></a><br></p><hr>
+
+<p><a name="rfc.section.2"></a></p><h3>2.&nbsp;
+Notation and Conventions</h3>
+
+<p>
+ The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”,
+ “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this
+ document are to be interpreted as described in <a class="info" href="#RFC2119">[RFC2119]<span> (</span><span class="info">Bradner, B., “Key words for use in RFCs to Indicate Requirement Levels,” .</span><span>)</span></a>.
+ Domain name examples use <a class="info" href="#RFC2606">[RFC2606]<span> (</span><span class="info">Eastlake, D. and A. Panitz, “Reserved Top Level DNS Names,” .</span><span>)</span></a>.
+
+</p>
+
+<p><a name="anchor3"></a><br></p><hr>
+
+<p><a name="rfc.section.3"></a></p><h3>3.&nbsp;
+Definitions</h3>
+
+<p>
+ </p>
+<blockquote class="text"><dl>
+<dt>Service Provider:</dt>
+<dd>
+ A web application that allows access via OAuth.
+
+</dd>
+<dt>User:</dt>
+<dd>
+ An individual who has an account with the Service Provider.
+
+</dd>
+<dt>Consumer:</dt>
+<dd>
+ A website or application that uses OAuth to access the Service
+ Provider on behalf of the User.
+
+</dd>
+<dt>Protected Resource(s):</dt>
+<dd>
+ Data controlled by the Service Provider, which the Consumer can
+ access through authentication.
+
+</dd>
+<dt>Consumer Developer:</dt>
+<dd>
+ An individual or organization that implements a Consumer.
+
+</dd>
+<dt>Consumer Key:</dt>
+<dd>
+ A value used by the Consumer to identify itself to the Service
+ Provider.
+
+</dd>
+<dt>Consumer Secret:</dt>
+<dd>
+ A secret used by the Consumer to establish ownership of the
+ Consumer Key.
+
+</dd>
+<dt>Request Token:</dt>
+<dd>
+ A value used by the Consumer to obtain authorization from the User,
+ and exchanged for an Access Token.
+
+</dd>
+<dt>Access Token:</dt>
+<dd>
+ A value used by the Consumer to gain access to the Protected
+ Resources on behalf of the User, instead of using the User’s
+ Service Provider credentials.
+
+</dd>
+<dt>Token Secret:</dt>
+<dd>
+ A secret used by the Consumer to establish ownership of a given
+ Token.
+
+</dd>
+<dt>OAuth Protocol Parameters:</dt>
+<dd>
+ Parameters with names beginning with <tt>oauth_</tt>.
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p><a name="anchor4"></a><br></p><hr>
+
+<p><a name="rfc.section.4"></a></p><h3>4.&nbsp;
+Documentation and Registration</h3>
+
+<p>
+ OAuth includes a Consumer Key and matching Consumer Secret that
+ together authenticate the Consumer (as opposed to the User) to the
+ Service Provider. Consumer-specific identification allows the Service
+ Provider to vary access levels to Consumers (such as un-throttled access
+ to resources).
+
+</p>
+
+<p>
+ Service Providers SHOULD NOT rely on the Consumer Secret as a method to
+ verify the Consumer identity, unless the Consumer Secret is known to be
+ inaccessible to anyone other than the Consumer and the Service
+ Provider. The Consumer Secret MAY be an empty string (for example when
+ no Consumer verification is needed, or when verification is achieved
+ through other means such as RSA).
+
+</p>
+
+<p><a name="request_urls"></a><br></p><hr>
+
+<p><a name="rfc.section.4.1"></a></p><h3>4.1.&nbsp;
+Request URLs</h3>
+
+<p>
+ OAuth defines three request URLs:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>Request Token URL:</dt>
+<dd>
+ The URL used to obtain an unauthorized Request Token, described
+ in <a class="info" href="#auth_step1">Section&nbsp;6.1<span> (</span><span class="info">Obtaining an Unauthorized Request Token</span><span>)</span></a>.
+
+</dd>
+<dt>User Authorization URL:</dt>
+<dd>
+ The URL used to obtain User authorization for Consumer access,
+ described in <a class="info" href="#auth_step2">Section&nbsp;6.2<span> (</span><span class="info">Obtaining User Authorization</span><span>)</span></a>.
+
+</dd>
+<dt>Access Token URL:</dt>
+<dd>
+ The URL used to exchange the User-authorized Request Token for
+ an Access Token, described in <a class="info" href="#auth_step3">Section&nbsp;6.3<span> (</span><span class="info">Obtaining an Access Token</span><span>)</span></a>.
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p>
+ The three URLs MUST include scheme, authority, and path, and MAY
+ include query and fragment as defined by <a class="info" href="#RFC3986">[RFC3986]<span> (</span><span class="info">Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a>
+ section 3. The request URL query MUST NOT contain any OAuth Protocol
+ Parameters. For example:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> http://sp.example.com/authorize
+</pre></div><p>
+
+
+</p>
+
+<p><a name="anchor5"></a><br></p><hr>
+
+<p><a name="rfc.section.4.2"></a></p><h3>4.2.&nbsp;
+Service Providers</h3>
+
+<p>
+ The Service Provider’s responsibility is to enable Consumer Developers
+ to establish a Consumer Key and Consumer Secret. The process and
+ requirements for provisioning these are entirely up to the Service
+ Providers.
+
+</p>
+
+<p>
+ The Service Provider’s documentation includes:
+
+ </p>
+<ol class="text">
+<li>
+ The <a class="info" href="#request_urls">URLs<span> (</span><span class="info">Request URLs</span><span>)</span></a> the Consumer will
+ use when making OAuth requests, and the HTTP methods (i.e. GET,
+ POST, etc.) used in the Request Token URL and Access Token URL.
+
+</li>
+<li>
+ Signature methods supported by the Service Provider.
+
+</li>
+<li>
+ Any additional request parameters that the Service Provider
+ requires in order to obtain a Token. Service Provider specific
+ parameters MUST NOT begin with <tt>oauth_</tt>.
+
+</li>
+</ol><p>
+
+</p>
+
+<p><a name="anchor6"></a><br></p><hr>
+
+<p><a name="rfc.section.4.3"></a></p><h3>4.3.&nbsp;
+Consumers</h3>
+
+<p>
+ The Consumer Developer MUST establish a Consumer Key and a Consumer
+ Secret with the Service Provider. The Consumer Developer MAY also be
+ required to provide additional information to the Service Provider
+ upon registration.
+
+</p>
+
+<p><a name="anchor7"></a><br></p><hr>
+
+<p><a name="rfc.section.5"></a></p><h3>5.&nbsp;
+Parameters</h3>
+
+<p>
+ OAuth Protocol Parameter names and values are case sensitive. Each
+ OAuth Protocol Parameters MUST NOT appear more than once per request,
+ and are REQUIRED unless otherwise noted.
+
+</p>
+
+<p><a name="encoding_parameters"></a><br></p><hr>
+
+<p><a name="rfc.section.5.1"></a></p><h3>5.1.&nbsp;
+Parameter Encoding</h3>
+
+<p>
+ All parameter names and values are escaped using the
+ <a class="info" href="#RFC3986">[RFC3986]<span> (</span><span class="info">Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a> percent-encoding (%xx) mechanism.
+ Characters not in the unreserved character set
+ (<a class="info" href="#RFC3986">[RFC3986]<span> (</span><span class="info">Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a> section 2.3) MUST be encoded. Characters
+ in the unreserved character set MUST NOT be encoded. Hexadecimal
+ characters in encodings MUST be upper case. Text names and values
+ MUST be encoded as UTF-8 octets before percent-encoding them per
+ <a class="info" href="#RFC3629">[RFC3629]<span> (</span><span class="info">Yergeau, F., “UTF-8, a transformation format of Unicode and ISO 10646,” .</span><span>)</span></a>.
+
+</p><div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> unreserved = ALPHA, DIGIT, '-', '.', '_', '~'
+</pre></div>
+<a name="consumer_req_param"></a><br><hr>
+
+<a name="rfc.section.5.2"></a><h3>5.2.&nbsp;
+Consumer Request Parameters</h3>
+
+<p>
+ OAuth Protocol Parameters are sent from the Consumer to the Service
+ Provider in one of three methods, in order of decreasing preference:
+ </p>
+<ol class="text">
+<li>
+ In the HTTP <tt>Authorization</tt> header as defined in
+ <a class="info" href="#auth_header">OAuth HTTP Authorization Scheme<span> (</span><span class="info">OAuth HTTP Authorization Scheme</span><span>)</span></a>.
+
+</li>
+<li>
+ As the HTTP POST request body with a <tt>
+ content-type
+ </tt> of
+ <tt>application/x-www-form-urlencoded</tt>.
+
+</li>
+<li>
+ Added to the URLs in the query part (as defined by
+ <a class="info" href="#RFC3986">[RFC3986]<span> (</span><span class="info">Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a> section 3).
+
+</li>
+</ol><p>
+
+</p>
+
+<p>
+ In addition to these defined methods, future extensions may describe
+ alternate methods for sending the OAuth Protocol Parameters.
+ The methods for sending other request parameters are left
+ undefined, but SHOULD NOT use the
+ <a class="info" href="#auth_header">OAuth HTTP Authorization Scheme<span> (</span><span class="info">OAuth HTTP Authorization Scheme</span><span>)</span></a> header.
+
+</p>
+
+<p><a name="response_parameters"></a><br></p><hr>
+
+<p><a name="rfc.section.5.3"></a></p><h3>5.3.&nbsp;
+Service Provider Response Parameters</h3>
+
+<p>
+ Response parameters are sent by the Service
+ Provider to return Tokens and other information to the Consumer in
+ the HTTP response body. The parameter names and values are first
+ encoded as per <a class="info" href="#encoding_parameters">Parameter Encoding<span> (</span><span class="info">Parameter Encoding</span><span>)</span></a>, and concatenated with the ‘&amp;’ character (ASCII code 38)
+ as defined in <a class="info" href="#RFC3986">[RFC3986]<span> (</span><span class="info">Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a> Section 2.1. For example:
+
+</p><div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> oauth_token=ab3cd9j4ks73hf7g&amp;oauth_token_secret=xyz4992k83j47x0b
+</pre></div>
+<a name="auth_header"></a><br><hr>
+
+<a name="rfc.section.5.4"></a><h3>5.4.&nbsp;
+OAuth HTTP Authorization Scheme</h3>
+
+<p>
+ This section defines an <a class="info" href="#RFC2617">[RFC2617]<span> (</span><span class="info">Franks,
+J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen,
+A., and L. Stewart, “HTTP Authentication: Basic and Digest Access
+Authentication,” .</span><span>)</span></a> extension to
+ support OAuth. It uses the standard HTTP <tt>Authorization</tt> and
+ <tt>WWW-Authenticate</tt> headers to pass OAuth Protocol Parameters.
+
+</p>
+
+<p>
+ It is RECOMMENDED that Service Providers accept the HTTP
+ <tt>Authorization</tt> header. Consumers SHOULD be able to send OAuth
+ Protocol Parameters in the OAuth <tt>Authorization</tt> header.
+
+</p>
+
+<p>
+ The extension auth-scheme (as defined by
+ <a class="info" href="#RFC2617">[RFC2617]<span> (</span><span class="info">Franks,
+J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen,
+A., and L. Stewart, “HTTP Authentication: Basic and Digest Access
+Authentication,” .</span><span>)</span></a>) is <tt>OAuth</tt> and is case-insensitive.
+
+</p>
+
+<p><a name="auth_header_authorization"></a><br></p><hr>
+
+<p><a name="rfc.section.5.4.1"></a></p><h3>5.4.1.&nbsp;
+Authorization Header</h3>
+
+<p>
+ The OAuth Protocol Parameters are sent in the <tt>Authorization</tt>
+ header the following way:
+
+ </p>
+<ol class="text">
+<li>
+ Parameter names and values are encoded per
+ <a class="info" href="#encoding_parameters">Parameter Encoding<span> (</span><span class="info">Parameter Encoding</span><span>)</span></a>.
+
+</li>
+<li>
+ For each parameter, the name is immediately followed by an ‘=’
+ character (ASCII code 61), a ‘”’ character (ASCII code 34), the
+ parameter value (MAY be empty), and another ‘”’ character
+ (ASCII code 34).
+
+</li>
+<li>
+ Parameters are separated by a comma character (ASCII code 44)
+ and OPTIONAL linear whitespace per <a class="info" href="#RFC2617">[RFC2617]<span> (</span><span class="info">Franks,
+J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen,
+A., and L. Stewart, “HTTP Authentication: Basic and Digest Access
+Authentication,” .</span><span>)</span></a>.
+
+</li>
+<li>
+ The OPTIONAL <tt>realm</tt> parameter is added and interpreted per
+ <a class="info" href="#RFC2617">[RFC2617]<span> (</span><span class="info">Franks,
+J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen,
+A., and L. Stewart, “HTTP Authentication: Basic and Digest Access
+Authentication,” .</span><span>)</span></a>, section 1.2.
+
+</li>
+</ol><p>
+
+</p>
+
+<p>
+ For example:
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> Authorization: OAuth realm="http://sp.example.com/",
+ oauth_consumer_key="0685bd9184jfhq22",
+ oauth_token="ad180jjd733klru7",
+ oauth_signature_method="HMAC-SHA1",
+ oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
+ oauth_timestamp="137131200",
+ oauth_nonce="4572616e48616d6d65724c61686176",
+ oauth_version="1.0"
+</pre></div><p>
+
+
+</p>
+
+<p><a name="anchor8"></a><br></p><hr>
+
+<p><a name="rfc.section.5.4.2"></a></p><h3>5.4.2.&nbsp;
+WWW-Authenticate Header</h3>
+
+<p>
+ Service Providers MAY indicate their support for the extension by
+ returning the OAuth HTTP <tt>WWW-Authenticate</tt>
+ header upon Consumer requests for Protected Resources. As per
+ <a class="info" href="#RFC2617">[RFC2617]<span> (</span><span class="info">Franks,
+J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen,
+A., and L. Stewart, “HTTP Authentication: Basic and Digest Access
+Authentication,” .</span><span>)</span></a> such a response MAY include additional
+ HTTP <tt>WWW-Authenticate</tt> headers:
+
+</p>
+
+<p>
+ For example:
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> WWW-Authenticate: OAuth realm="http://sp.example.com/"
+</pre></div><p>
+
+
+</p>
+
+<p>
+ The realm parameter defines a protection realm per
+ <a class="info" href="#RFC2617">[RFC2617]<span> (</span><span class="info">Franks,
+J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen,
+A., and L. Stewart, “HTTP Authentication: Basic and Digest Access
+Authentication,” .</span><span>)</span></a>, section 1.2.
+
+</p>
+
+<p><a name="anchor9"></a><br></p><hr>
+
+<p><a name="rfc.section.6"></a></p><h3>6.&nbsp;
+Authenticating with OAuth</h3>
+
+<p>
+ OAuth authentication is the process in which Users grant access to
+ their Protected Resources without sharing their credentials with the
+ Consumer. OAuth uses Tokens generated by the Service Provider instead
+ of the User’s credentials in Protected Resources requests. The process
+ uses two Token types:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>Request Token:</dt>
+<dd>
+ Used by the Consumer to ask the User to authorize access to the
+ Protected Resources. The User-authorized Request Token is exchanged
+ for an Access Token, MUST only be used once, and MUST NOT be used
+ for any other purpose. It is RECOMMENDED that Request Tokens have
+ a limited lifetime.
+
+</dd>
+<dt>Access Token:</dt>
+<dd>
+ Used by the Consumer to access the Protected Resources on behalf of
+ the User. Access Tokens MAY limit access to certain Protected
+ Resources, and MAY have a limited lifetime. Service Providers
+ SHOULD allow Users to revoke Access Tokens. Only the Access Token
+ SHALL be used to access the Protect Resources.
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p>
+ OAuth Authentication is done in three steps:
+
+ </p>
+<ol class="text">
+<li>
+ The Consumer obtains an unauthorized Request Token.
+
+</li>
+<li>
+ The User authorizes the Request Token.
+
+</li>
+<li>
+ The Consumer exchanges the Request Token for an Access Token.
+
+</li>
+</ol><p>
+
+</p>
+
+<p><img src="OAuth%20Core%201.0_files/diagram.png" alt=""></p>
+<a name="auth_step1"></a><br><hr>
+
+<a name="rfc.section.6.1"></a><h3>6.1.&nbsp;
+Obtaining an Unauthorized Request Token</h3>
+
+<p>
+ The Consumer obtains an unauthorized Request Token by asking the
+ Service Provider to issue a Token. The Request Token’s sole purpose
+ is to receive User approval and can only be used to obtain an Access
+ Token. The Request Token process goes as follows:
+
+</p>
+
+<p><a name="obtain_request_token"></a><br></p><hr>
+
+<p><a name="rfc.section.6.1.1"></a></p><h3>6.1.1.&nbsp;
+Consumer Obtains a Request Token</h3>
+
+<p>
+ To obtain a Request Token, the Consumer sends an HTTP request to
+ the Service Provider’s Request Token URL. The Service Provider
+ documentation specifies the HTTP method for this request, and HTTP POST
+ is RECOMMENDED. The request MUST be signed and contains the following parameters:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>oauth_consumer_key:</dt>
+<dd>
+ The Consumer Key.
+
+</dd>
+<dt>oauth_signature_method:</dt>
+<dd>
+ The signature method the Consumer used to sign the request.
+
+</dd>
+<dt>oauth_signature:</dt>
+<dd>
+ The signature as defined in
+ <a class="info" href="#signing_process">Signing Requests<span> (</span><span class="info">Signing Requests</span><span>)</span></a>.
+
+</dd>
+<dt>oauth_timestamp:</dt>
+<dd>
+ As defined in <a class="info" href="#nonce">Nonce and Timestamp<span> (</span><span class="info">Nonce and Timestamp</span><span>)</span></a>.
+
+</dd>
+<dt>oauth_nonce:</dt>
+<dd>
+ As defined in <a class="info" href="#nonce">Nonce and Timestamp<span> (</span><span class="info">Nonce and Timestamp</span><span>)</span></a>.
+
+</dd>
+<dt>oauth_version:</dt>
+<dd>
+ OPTIONAL. If present, value MUST be <tt>
+ 1.0
+ </tt>. Service Providers
+ MUST assume the protocol version to be <tt>1.0</tt> if this parameter
+ is not present. Service Providers’ response to non-<tt>1.0</tt> value
+ is left undefined.
+
+</dd>
+<dt>Additional parameters:</dt>
+<dd>
+ Any additional parameters, as defined by the Service Provider.
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p><a name="request_grant"></a><br></p><hr>
+
+<p><a name="rfc.section.6.1.2"></a></p><h3>6.1.2.&nbsp;
+Service Provider Issues an Unauthorized Request Token</h3>
+
+<p>
+ The Service Provider verifies the signature and Consumer Key. If
+ successful, it generates a Request Token and Token Secret and
+ returns them to the Consumer in the HTTP response body as defined
+ in <a class="info" href="#response_parameters">Service Provider Response Parameters<span> (</span><span class="info">Service Provider Response Parameters</span><span>)</span></a>.
+ The Service Provider MUST ensure the Request
+ Token cannot be exchanged for an Access Token until the User
+ successfully grants access in <a class="info" href="#auth_step2">Obtaining
+ User Authorization<span> (</span><span class="info">Obtaining User Authorization</span><span>)</span></a>.
+
+</p>
+
+<p>
+ The response contains the following parameters:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>oauth_token:</dt>
+<dd>
+ The Request Token.
+
+</dd>
+<dt>oauth_token_secret:</dt>
+<dd>
+ The Token Secret.
+
+</dd>
+<dt>Additional parameters:</dt>
+<dd>
+ Any additional parameters, as defined by the Service Provider.
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p>
+ If the request fails verification or is rejected for other reasons,
+ the Service Provider SHOULD respond with the appropriate response
+ code as defined in <a class="info" href="#http_codes">HTTP Response Codes<span> (</span><span class="info">HTTP Response Codes</span><span>)</span></a>.
+ The Service Provider MAY include some further details about why the
+ request was rejected in the HTTP response body as defined in
+ <a class="info" href="#response_parameters">Service Provider Response Parameters<span> (</span><span class="info">Service Provider Response Parameters</span><span>)</span></a>.
+
+</p>
+
+<p><a name="auth_step2"></a><br></p><hr>
+
+<p><a name="rfc.section.6.2"></a></p><h3>6.2.&nbsp;
+Obtaining User Authorization</h3>
+
+<p>
+ The Consumer cannot use the Request Token until it has been
+ authorized by the User. Obtaining User authorization includes
+ the following steps:
+
+</p>
+
+<p><a name="user_auth_redirected"></a><br></p><hr>
+
+<p><a name="rfc.section.6.2.1"></a></p><h3>6.2.1.&nbsp;
+Consumer Directs the User to the Service Provider</h3>
+
+<p>
+ In order for the Consumer to be able to exchange the Request Token
+ for an Access Token, the Consumer MUST obtain approval from the
+ User by directing the User to the Service Provider. The Consumer
+ constructs an HTTP GET request to the Service Provider’s
+ User Authorization URL with the following parameter:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>oauth_token:</dt>
+<dd>
+ OPTIONAL. The Request Token obtained in the previous step. The
+ Service Provider MAY declare this parameter as REQUIRED, or
+ accept requests to the User Authorization URL without it, in
+ which case it will prompt the User to enter it manually.
+
+</dd>
+<dt>oauth_callback:</dt>
+<dd>
+ OPTIONAL. The Consumer MAY specify a URL the Service Provider
+ will use to redirect the User back to the Consumer when
+ <a class="info" href="#auth_step2">Obtaining User Authorization<span> (</span><span class="info">Obtaining User Authorization</span><span>)</span></a>
+ is complete.
+
+</dd>
+<dt>Additional parameters:</dt>
+<dd>
+ Any additional parameters, as defined by the Service Provider.
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p>
+ Once the request URL has been constructed the Consumer redirects
+ the User to the URL via the User’s web browser. If the Consumer is
+ incapable of automatic HTTP redirection, the Consumer SHALL notify
+ the User how to manually go to the constructed request URL.
+
+</p>
+
+<p>
+ Note: If a Service Provider knows a Consumer to be running on a
+ mobile device or set-top box, the Service Provider SHOULD ensure
+ that the User Authorization URL and Request Token are suitable
+ for manual entry.
+
+</p>
+
+<p><a name="anchor10"></a><br></p><hr>
+
+<p><a name="rfc.section.6.2.2"></a></p><h3>6.2.2.&nbsp;
+Service Provider Authenticates the User and Obtains Consent</h3>
+
+<p>
+ The Service Provider verifies the User’s identity and asks for
+ consent as detailed. OAuth does not specify how the Service Provider
+ authenticates the User. However, it does define a set of REQUIRED
+ steps:
+
+ </p>
+<ul class="text">
+<li>
+ The Service Provider MUST first verify the User’s identity
+ before asking for consent. It MAY prompt the User to sign
+ in if the User has not already done so.
+
+</li>
+<li>
+ The Service Provider presents to the User information about the
+ Consumer requesting access (as registered by the Consumer
+ Developer). The information includes the duration of the
+ access and the Protected Resources provided. The information
+ MAY include other details specific to the Service Provider.
+
+</li>
+<li>
+ The User MUST grant or deny permission for the Service Provider
+ to give the Consumer access to the Protected Resources on
+ behalf of the User. If the User denies the Consumer access, the
+ Service Provider MUST NOT allow access to the Protected
+ Resources.
+
+</li>
+</ul><p>
+
+</p>
+
+<p>
+ When displaying any identifying information about the Consumer to
+ the User based on the Consumer Key, the Service Provider MUST
+ inform the User if it is unable to assure the Consumer’s true
+ identity. The method in which the Service Provider informs the User
+ and the quality of the identity assurance is beyond the scope of
+ this specification.
+
+</p>
+
+<p><a name="anchor11"></a><br></p><hr>
+
+<p><a name="rfc.section.6.2.3"></a></p><h3>6.2.3.&nbsp;
+Service Provider Directs the User Back to the Consumer</h3>
+
+<p>
+ After the User authenticates with the Service Provider and grants
+ permission for Consumer access, the Consumer MUST be notified that
+ the Request Token has been authorized and ready to be exchanged for
+ an Access Token. If the User denies access, the Consumer MAY be
+ notified that the Request Token has been revoked.
+
+</p>
+
+<p>
+ If the Consumer provided a callback URL in <tt>oauth_callback</tt> (as
+ described in <a class="info" href="#user_auth_redirected">Consumer Directs the User to the Service Provider<span> (</span><span class="info">Consumer Directs the User to the Service Provider</span><span>)</span></a>),
+ the Service Provider constructs an HTTP GET request URL, and
+ redirects the User’s web browser to that URL with the following
+ parameters:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>oauth_token:</dt>
+<dd>
+ The Request Token the User authorized or denied.
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p>
+ The callback URL MAY include Consumer provided query parameters.
+ The Service Provider MUST retain them unmodified and append the
+ <tt>oauth_token</tt> parameter to the existing query.
+
+</p>
+
+<p>
+ If no callback URL was provided, the Service Provider instructs
+ the User to manually inform the Consumer that authorization has
+ completed.
+
+</p>
+
+<p><a name="auth_step3"></a><br></p><hr>
+
+<p><a name="rfc.section.6.3"></a></p><h3>6.3.&nbsp;
+Obtaining an Access Token</h3>
+
+<p>
+ The Consumer exchanges the Request Token for an Access Token capable
+ of accessing the Protected Resources. Obtaining an Access Token
+ includes the following steps:
+
+</p>
+
+<p><a name="anchor12"></a><br></p><hr>
+
+<p><a name="rfc.section.6.3.1"></a></p><h3>6.3.1.&nbsp;
+Consumer Requests an Access Token</h3>
+
+<p>
+ The Request Token and Token Secret MUST be exchanged for an Access
+ Token and Token Secret.
+
+</p>
+
+<p>
+ To request an Access Token, the Consumer makes an HTTP request to
+ the Service Provider’s Access Token URL. The Service Provider
+ documentation specifies the HTTP method for this request, and HTTP POST
+ is RECOMMENDED. The request MUST be signed per
+ <a class="info" href="#signing_process">Signing Requests<span> (</span><span class="info">Signing Requests</span><span>)</span></a>,
+ and contains the following parameters:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>oauth_consumer_key:</dt>
+<dd>
+ The Consumer Key.
+
+</dd>
+<dt>oauth_token:</dt>
+<dd>
+ The Request Token obtained previously.
+
+</dd>
+<dt>oauth_signature_method:</dt>
+<dd>
+ The signature method the Consumer used to sign the request.
+
+</dd>
+<dt>oauth_signature:</dt>
+<dd>
+ The signature as defined in <a class="info" href="#signing_process">Signing Requests<span> (</span><span class="info">Signing Requests</span><span>)</span></a>.
+
+</dd>
+<dt>oauth_timestamp:</dt>
+<dd>
+ As defined in <a class="info" href="#nonce">Nonce and Timestamp<span> (</span><span class="info">Nonce and Timestamp</span><span>)</span></a>.
+
+</dd>
+<dt>oauth_nonce:</dt>
+<dd>
+ As defined in <a class="info" href="#nonce">Nonce and Timestamp<span> (</span><span class="info">Nonce and Timestamp</span><span>)</span></a>.
+
+</dd>
+<dt>oauth_version:</dt>
+<dd>
+ OPTIONAL. If present, value MUST be <tt>
+ 1.0
+ </tt>. Service Providers
+ MUST assume the protocol version to be <tt>1.0</tt> if this parameter
+ is not present. Service Providers’ response to non-<tt>1.0</tt> value
+ is left undefined.
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p>
+ No additional Service Provider specific parameters are allowed when
+ requesting an Access Token to ensure all Token related information
+ is present prior to seeking User approval.
+
+</p>
+
+<p><a name="access_grant"></a><br></p><hr>
+
+<p><a name="rfc.section.6.3.2"></a></p><h3>6.3.2.&nbsp;
+Service Provider Grants an Access Token</h3>
+
+<p>
+ The Service Provider MUST ensure that:
+
+ </p>
+<ul class="text">
+<li>
+ The request signature has been successfully verified.
+
+</li>
+<li>
+ The Request Token has never been exchanged for an Access Token.
+
+</li>
+<li>
+ The Request Token matches the Consumer Key.
+
+</li>
+</ul><p>
+
+</p>
+
+<p>
+ If successful, the Service Provider generates an Access Token and
+ Token Secret and returns them in the HTTP response body as defined
+ in <a class="info" href="#response_parameters">Service Provider Response Parameters<span> (</span><span class="info">Service Provider Response Parameters</span><span>)</span></a>.
+ The Access Token and Token Secret are stored by the Consumer and
+ used when signing Protected Resources requests. The response
+ contains the following parameters:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>oauth_token:</dt>
+<dd>
+ The Access Token.
+
+</dd>
+<dt>oauth_token_secret:</dt>
+<dd>
+ The Token Secret.
+
+</dd>
+<dt>Additional parameters:</dt>
+<dd>
+ Any additional parameters, as defined by the Service Provider.
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p>
+ If the request fails verification or is rejected for other reasons,
+ the Service Provider SHOULD respond with the appropriate response
+ code as defined in <a class="info" href="#http_codes">HTTP Response Codes<span> (</span><span class="info">HTTP Response Codes</span><span>)</span></a>.
+ The Service Provider MAY include some further details about why the
+ request was rejected in the HTTP response body as defined in
+ <a class="info" href="#response_parameters">Service Provider Response Parameters<span> (</span><span class="info">Service Provider Response Parameters</span><span>)</span></a>.
+
+</p>
+
+<p><a name="anchor13"></a><br></p><hr>
+
+<p><a name="rfc.section.7"></a></p><h3>7.&nbsp;
+Accessing Protected Resources</h3>
+
+<p>
+ After successfully receiving the Access Token and Token Secret, the
+ Consumer is able to access the Protected Resources on behalf of the
+ User. The request MUST be signed per
+ <a class="info" href="#signing_process">Signing Requests<span> (</span><span class="info">Signing Requests</span><span>)</span></a>, and
+ contains the following parameters:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>oauth_consumer_key:</dt>
+<dd>
+ The Consumer Key.
+
+</dd>
+<dt>oauth_token:</dt>
+<dd>
+ The Access Token.
+
+</dd>
+<dt>oauth_signature_method:</dt>
+<dd>
+ The signature method the Consumer used to sign the request.
+
+</dd>
+<dt>oauth_signature:</dt>
+<dd>
+ The signature as defined in
+ <a class="info" href="#signing_process">Signing Requests<span> (</span><span class="info">Signing Requests</span><span>)</span></a>.
+
+</dd>
+<dt>oauth_timestamp:</dt>
+<dd>
+ As defined in <a class="info" href="#nonce">Nonce and Timestamp<span> (</span><span class="info">Nonce and Timestamp</span><span>)</span></a>.
+
+</dd>
+<dt>oauth_nonce:</dt>
+<dd>
+ As defined in <a class="info" href="#nonce">Nonce and Timestamp<span> (</span><span class="info">Nonce and Timestamp</span><span>)</span></a>.
+
+</dd>
+<dt>oauth_version:</dt>
+<dd>
+ OPTIONAL. If present, value MUST be <tt>1.0</tt>. Service Providers
+ MUST assume the protocol version to be <tt>1.0</tt> if this parameter
+ is not present. Service Providers’ response to non-<tt>1.0</tt> value
+ is left undefined.
+
+</dd>
+<dt>Additional parameters:</dt>
+<dd>
+ Any additional parameters, as defined by the Service Provider.
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p><a name="nonce"></a><br></p><hr>
+
+<p><a name="rfc.section.8"></a></p><h3>8.&nbsp;
+Nonce and Timestamp</h3>
+
+<p>
+ Unless otherwise specified by the Service Provider, the timestamp is
+ expressed in the number of seconds since January 1, 1970 00:00:00 GMT.
+ The timestamp value MUST be a positive integer and MUST be equal or
+ greater than the timestamp used in previous requests.
+
+</p>
+
+<p>
+ The Consumer SHALL then generate a Nonce value that is unique for all
+ requests with that timestamp. A nonce is a random string, uniquely
+ generated for each request. The nonce allows the Service Provider to
+ verify that a request has never been made before and helps prevent
+ replay attacks when requests are made over a non-secure channel
+ (such as HTTP).
+
+</p>
+
+<p><a name="signing_process"></a><br></p><hr>
+
+<p><a name="rfc.section.9"></a></p><h3>9.&nbsp;
+Signing Requests</h3>
+
+<p>
+ All Token requests and Protected Resources requests MUST be
+ signed by the Consumer and verified by the Service Provider.
+ The purpose of signing requests is to prevent unauthorized parties
+ from using the Consumer Key and Tokens when making Token requests or
+ Protected Resources requests. The signature process encodes
+ the Consumer Secret and Token Secret into a verifiable value which is
+ included with the request.
+
+</p>
+
+<p>
+ OAuth does not mandate a particular signature method, as each
+ implementation can have its own unique requirements. The protocol
+ defines three signature methods: <tt>HMAC-SHA1</tt>,
+ <tt>RSA-SHA1</tt>, and
+ <tt>PLAINTEXT</tt>, but Service Providers
+ are free to implement and document their own methods.
+ Recommending any particular method is beyond the scope of this specification.
+
+</p>
+
+<p>
+ The Consumer declares a signature method in the <tt>oauth_signature_method</tt>
+ parameter, generates a signature, and stores it in the <tt>oauth_signature</tt>
+ parameter. The Service Provider verifies the signature as specified in
+ each method. When verifying a Consumer signature, the Service Provider
+ SHOULD check the request nonce to ensure it has not been used in a
+ previous Consumer request.
+
+</p>
+
+<p>
+ The signature process MUST NOT change the request parameter names or
+ values, with the exception of the <tt>oauth_signature</tt> parameter.
+
+</p>
+
+<p><a name="anchor14"></a><br></p><hr>
+
+<p><a name="rfc.section.9.1"></a></p><h3>9.1.&nbsp;
+Signature Base String</h3>
+
+<p>
+ The Signature Base String is a consistent reproducible concatenation
+ of the request elements into a single string. The string is used as an
+ input in hashing or signing algorithms. The <tt>HMAC-SHA1</tt> signature
+ method provides both a standard and an example of using the Signature
+ Base String with a signing algorithm to generate signatures. All
+ the request parameters MUST be encoded as described in
+ <a class="info" href="#encoding_parameters">Parameter Encoding<span> (</span><span class="info">Parameter Encoding</span><span>)</span></a> prior to
+ constructing the Signature Base String.
+
+</p>
+
+<p><a name="sig_norm_param"></a><br></p><hr>
+
+<p><a name="rfc.section.9.1.1"></a></p><h3>9.1.1.&nbsp;
+Normalize Request Parameters</h3>
+
+<p>
+ The request parameters are collected, sorted and concatenated into
+ a normalized string:
+
+ </p>
+<ul class="text">
+<li>
+ Parameters in the <a class="info" href="#auth_header_authorization">OAuth HTTP Authorization header<span> (</span><span class="info">Authorization Header</span><span>)</span></a> excluding the <tt>realm</tt>
+ parameter.
+
+</li>
+<li>
+ Parameters in the HTTP POST request body (with a
+ <tt>content-type</tt> of
+ <tt>application/x-www-form-urlencoded</tt>).
+
+</li>
+<li>
+ HTTP GET parameters added to the URLs in the query part (as defined by
+ <a class="info" href="#RFC3986">[RFC3986]<span> (</span><span class="info">Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a> section 3).
+
+</li>
+</ul><p>
+
+</p>
+
+<p>
+ The <tt>oauth_signature</tt> parameter MUST be
+ excluded.
+
+</p>
+
+<p>
+ The parameters are normalized into a single string as follows:
+
+ </p>
+<ol class="text">
+<li>
+ Parameters are sorted by name, using lexicographical byte value
+ ordering. If two or more parameters share the same name, they
+ are sorted by their value. For example:
+
+ <div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> a=1, c=hi%20there, f=25, f=50, f=a, z=p, z=t
+</pre></div>
+
+</li>
+<li>
+ Parameters are concatenated in their sorted order into a single
+ string. For each parameter, the name is separated from the
+ corresponding value by an ‘=’ character (ASCII code 61), even
+ if the value is empty. Each name-value pair is separated by an
+ ‘&amp;’ character (ASCII code 38). For example:
+
+ <div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> a=1&amp;c=hi%20there&amp;f=25&amp;f=50&amp;f=a&amp;z=p&amp;z=t
+</pre></div>
+
+</li>
+</ol><p>
+
+</p>
+
+<p><a name="sig_url"></a><br></p><hr>
+
+<p><a name="rfc.section.9.1.2"></a></p><h3>9.1.2.&nbsp;
+Construct Request URL</h3>
+
+<p>
+ The Signature Base String includes the request absolute URL, tying
+ the signature to a specific endpoint. The URL used in the Signature
+ Base String MUST include the scheme, authority, and path, and MUST
+ exclude the query and fragment as defined by <a class="info" href="#RFC3986">[RFC3986]<span> (</span><span class="info">Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .</span><span>)</span></a>
+ section 3.
+
+</p>
+
+<p>
+ If the absolute request URL is not available to the Service Provider
+ (it is always available to the Consumer), it can be constructed by
+ combining the scheme being used, the HTTP <tt>Host</tt>
+ header, and the relative HTTP request URL. If the
+ <tt>Host</tt> header is not available, the Service
+ Provider SHOULD use the host name communicated to the Consumer in the
+ documentation or other means.
+
+</p>
+
+<p>
+ The Service Provider SHOULD document the form of URL used in the
+ Signature Base String to avoid ambiguity due to URL normalization.
+ Unless specified, URL scheme and authority MUST be lowercase and
+ include the port number; <tt>http</tt> default
+ port 80 and <tt>https</tt> default port 443 MUST
+ be excluded.
+
+</p>
+
+<p>
+ For example, the request:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> HTTP://Example.com:80/resource?id=123
+</pre></div><p>
+
+
+ Is included in the Signature Base String as:
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> http://example.com/resource
+</pre></div><p>
+
+
+</p>
+
+<p><a name="anchor15"></a><br></p><hr>
+
+<p><a name="rfc.section.9.1.3"></a></p><h3>9.1.3.&nbsp;
+Concatenate Request Elements</h3>
+
+<p>
+ The following items MUST be concatenated in order into a single
+ string. Each item is <a class="info" href="#encoding_parameters">encoded<span> (</span><span class="info">Parameter Encoding</span><span>)</span></a>
+ and separated by an ‘&amp;’ character (ASCII code 38), even if empty.
+
+ </p>
+<ol class="text">
+<li>
+ The HTTP request method used to send the request. Value MUST be
+ uppercase, for example: <tt>HEAD</tt>, <tt>
+ GET
+ </tt>, <tt>POST</tt>, etc.
+
+</li>
+<li>
+ The request URL from <a class="info" href="#sig_url">Section&nbsp;9.1.2<span> (</span><span class="info">Construct Request URL</span><span>)</span></a>.
+
+</li>
+<li>
+ The normalized request parameters string from <a class="info" href="#sig_norm_param">Section&nbsp;9.1.1<span> (</span><span class="info">Normalize Request Parameters</span><span>)</span></a>.
+
+</li>
+</ol><p>
+
+</p>
+
+<p>
+ See Signature Base String example in <a class="info" href="#sig_base_example">Appendix&nbsp;A.5.1<span> (</span><span class="info">Generating Signature Base String</span><span>)</span></a>.
+
+</p>
+
+<p><a name="anchor16"></a><br></p><hr>
+
+<p><a name="rfc.section.9.2"></a></p><h3>9.2.&nbsp;
+HMAC-SHA1</h3>
+
+<p>
+ The <tt>HMAC-SHA1</tt> signature method uses the HMAC-SHA1 signature
+ algorithm as defined in <a class="info" href="#RFC2104">[RFC2104]<span> (</span><span class="info">Krawczyk, H., Bellare, M., and R. Canetti, “HMAC: Keyed-Hashing for Message Authentication,” .</span><span>)</span></a> where the Signature
+ Base String is the <tt>text</tt> and the
+ <tt>key</tt> is the concatenated values
+ (each first encoded per <a class="info" href="#encoding_parameters">Parameter Encoding<span> (</span><span class="info">Parameter Encoding</span><span>)</span></a>)
+ of the Consumer Secret and Token Secret, separated by an ‘&amp;’
+ character (ASCII code 38) even if empty.
+
+</p>
+
+<p><a name="anchor17"></a><br></p><hr>
+
+<p><a name="rfc.section.9.2.1"></a></p><h3>9.2.1.&nbsp;
+Generating Signature</h3>
+
+<p>
+ <tt>oauth_signature</tt> is set
+ to the calculated <tt>digest</tt> octet string, first base64-encoded per
+ <a class="info" href="#RFC2045">[RFC2045]<span> (</span><span class="info">Freed, N. and N. Borenstein, “Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies,” .</span><span>)</span></a> section 6.8, then URL-encoded per
+ <a class="info" href="#encoding_parameters">Parameter Encoding<span> (</span><span class="info">Parameter Encoding</span><span>)</span></a>.
+
+</p>
+
+<p><a name="anchor18"></a><br></p><hr>
+
+<p><a name="rfc.section.9.2.2"></a></p><h3>9.2.2.&nbsp;
+Verifying Signature</h3>
+
+<p>
+ The Service Provider verifies the request by generating a new request
+ signature octet string, and comparing it to the signature provided by the Consumer,
+ first URL-decoded per <a class="info" href="#encoding_parameters">Parameter Encoding<span> (</span><span class="info">Parameter Encoding</span><span>)</span></a>,
+ then base64-decoded per <a class="info" href="#RFC2045">[RFC2045]<span> (</span><span class="info">Freed, N. and N. Borenstein, “Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies,” .</span><span>)</span></a> section 6.8.
+ The signature is generated using the request parameters as provided
+ by the Consumer, and the Consumer Secret and Token Secret as stored
+ by the Service Provider.
+
+</p>
+
+<p><a name="anchor19"></a><br></p><hr>
+
+<p><a name="rfc.section.9.3"></a></p><h3>9.3.&nbsp;
+RSA-SHA1</h3>
+
+<p>
+ The <tt>RSA-SHA1</tt> signature method uses the
+ RSASSA-PKCS1-v1_5 signature algorithm as defined in
+ <a class="info" href="#RFC3447">[RFC3447]<span> (</span><span class="info">Jonsson, J. and B. Kaliski, “Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography; Specifications Version 2.1,” .</span><span>)</span></a> section 8.2 (more simply known as PKCS#1),
+ using SHA-1 as the hash function for EMSA-PKCS1-v1_5. It is assumed
+ that the Consumer has provided its RSA public key in a verified way
+ to the Service Provider, in a manner which is beyond the scope of
+ this specification.
+
+</p>
+
+<p><a name="anchor20"></a><br></p><hr>
+
+<p><a name="rfc.section.9.3.1"></a></p><h3>9.3.1.&nbsp;
+Generating Signature</h3>
+
+<p>
+ The Signature Base String is signed using the Consumer’s RSA private
+ key per <a class="info" href="#RFC3447">[RFC3447]<span> (</span><span class="info">Jonsson, J. and B. Kaliski, “Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography; Specifications Version 2.1,” .</span><span>)</span></a> section 8.2.1, where <tt>K</tt> is the
+ Consumer’s RSA private key, <tt>M</tt> the Signature Base String, and <tt>S</tt> is
+ the result signature octet string:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> S = RSASSA-PKCS1-V1_5-SIGN (K, M)
+</pre></div><p>
+
+
+</p>
+
+<p>
+ <tt>oauth_signature</tt> is set to <tt>S</tt>, first base64-encoded per
+ <a class="info" href="#RFC2045">[RFC2045]<span> (</span><span class="info">Freed, N. and N. Borenstein, “Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies,” .</span><span>)</span></a> section 6.8, then URL-encoded per
+ <a class="info" href="#encoding_parameters">Parameter Encoding<span> (</span><span class="info">Parameter Encoding</span><span>)</span></a>.
+
+</p>
+
+<p><a name="anchor21"></a><br></p><hr>
+
+<p><a name="rfc.section.9.3.2"></a></p><h3>9.3.2.&nbsp;
+Verifying Signature</h3>
+
+<p>
+ The Service Provider verifies the signature per <a class="info" href="#RFC3447">[RFC3447]<span> (</span><span class="info">Jonsson, J. and B. Kaliski, “Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography; Specifications Version 2.1,” .</span><span>)</span></a>
+ section 8.2.2, where <tt>
+ (n, e)
+ </tt> is the Consumer’s RSA public key, <tt>M</tt>
+ is the Signature Base String, and <tt>S</tt> is the octet string
+ representation of the <tt>oauth_signature</tt> value:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S)
+</pre></div><p>
+
+
+</p>
+
+<p><a name="anchor22"></a><br></p><hr>
+
+<p><a name="rfc.section.9.4"></a></p><h3>9.4.&nbsp;
+PLAINTEXT</h3>
+
+<p>
+ The <tt>
+ PLAINTEXT
+ </tt> method does not provide any security protection and
+ SHOULD only be used over a secure channel such as HTTPS. It does not
+ use the Signature Base String.
+
+</p>
+
+<p><a name="anchor23"></a><br></p><hr>
+
+<p><a name="rfc.section.9.4.1"></a></p><h3>9.4.1.&nbsp;
+Generating Signature</h3>
+
+<p>
+ <tt>oauth_signature</tt> is set to the concatenated encoded values of the
+ Consumer Secret and Token Secret, separated by a ‘&amp;’ character (ASCII
+ code 38), even if either secret is empty. The result MUST be encoded again.
+
+</p>
+
+<p>
+ These examples show the value of <tt>oauth_signature</tt>
+ for Consumer Secret <tt>djr9rjt0jd78jf88</tt> and
+ 3 different Token Secrets:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>jjd999tj88uiths3:</dt>
+<dd>
+ <tt>oauth_signature</tt>=<tt>djr9rjt0jd78jf88%26jjd999tj88uiths3</tt>
+
+</dd>
+<dt>jjd99$tj88uiths3:</dt>
+<dd>
+ <tt>oauth_signature</tt>=<tt>djr9rjt0jd78jf88%26jjd99%2524tj88uiths3</tt>
+
+</dd>
+<dt>Empty:</dt>
+<dd>
+ <tt>oauth_signature</tt>=<tt>djr9rjt0jd78jf88%26</tt>
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p><a name="anchor24"></a><br></p><hr>
+
+<p><a name="rfc.section.9.4.2"></a></p><h3>9.4.2.&nbsp;
+Verifying Signature</h3>
+
+<p>
+ The Service Provider verifies the request by breaking the signature
+ value into the Consumer Secret and Token Secret, and ensures they
+ match the secrets stored locally.
+
+</p>
+
+<p><a name="http_codes"></a><br></p><hr>
+
+<p><a name="rfc.section.10"></a></p><h3>10.&nbsp;
+HTTP Response Codes</h3>
+
+<p>
+ This section applies only to the Request Token and Access Token
+ requests. In general, the Service Provider SHOULD use the
+ response codes defined in <a class="info" href="#RFC2616">[RFC2616]<span> (</span><span class="info">Fielding,
+R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P., and T.
+Berners-Lee, “Hypertext Transfer Protocol – HTTP/1.1,” .</span><span>)</span></a> Section 10. When
+ the Service Provider rejects a Consumer request, it SHOULD respond with
+ HTTP 400 Bad Request or HTTP 401 Unauthorized.
+
+ </p>
+<ul class="text">
+<li>
+ HTTP 400 Bad Request
+
+<ul class="text">
+<li>
+ Unsupported parameter
+
+</li>
+<li>
+ Unsupported signature method
+
+</li>
+<li>
+ Missing required parameter
+
+</li>
+<li>
+ Duplicated OAuth Protocol Parameter
+
+</li>
+</ul>
+
+</li>
+<li>
+ HTTP 401 Unauthorized
+
+<ul class="text">
+<li>
+ Invalid Consumer Key
+
+</li>
+<li>
+ Invalid / expired Token
+
+</li>
+<li>
+ Invalid signature
+
+</li>
+<li>
+ Invalid / used nonce
+
+</li>
+</ul>
+
+</li>
+</ul><p>
+
+</p>
+
+<p><a name="anchor25"></a><br></p><hr>
+
+<p><a name="rfc.section.A"></a></p><h3>Appendix A.&nbsp;
+Appendix A - Protocol Example</h3>
+
+<p>
+ In this example, the Service Provider photos.example.net is a photo
+ sharing website, and the Consumer printer.example.com is a photo
+ printing website. Jane, the User, would like printer.example.com to
+ print the private photo <tt>
+ vacation.jpg
+ </tt> stored at photos.example.net.
+
+</p>
+
+<p>
+ When Jane signs-into photos.example.net using her username and
+ password, she can access the photo by going to the URL
+ <tt>http://photos.example.net/photo?file=vacation.jpg</tt>. Other Users
+ cannot access that photo, and Jane does not want to share her
+ username and password with printer.example.com.
+
+</p>
+
+<p>
+ The requests in this example use the URL query method when sending
+ parameters. This is done to simplify the example and should not be
+ taken as an endorsement of one method over the others.
+
+</p>
+
+<p><a name="anchor26"></a><br></p><hr>
+
+<p><a name="rfc.section.A.1"></a></p><h3>Appendix A.1.&nbsp;
+Documentation and Registration</h3>
+
+<p>
+ The Service Provider documentation explains how to register for a
+ Consumer Key and Consumer Secret, and declares the following URLs:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>Request Token URL:</dt>
+<dd>
+ https://photos.example.net/request_token, using HTTP POST
+
+</dd>
+<dt>User Authorization URL:</dt>
+<dd>
+ http://photos.example.net/authorize, using HTTP GET
+
+</dd>
+<dt>Access Token URL:</dt>
+<dd>
+ https://photos.example.net/access_token, using HTTP POST
+
+</dd>
+<dt>Photo (Protected Resource) URL:</dt>
+<dd>
+ http://photos.example.net/photo with required parameter
+ <tt>file</tt> and optional parameter <tt>size</tt>
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p>
+ The Service Provider declares support for the <tt>
+ HMAC-SHA1
+ </tt> signature
+ method for all requests, and <tt>PLAINTEXT</tt> only for secure (HTTPS)
+ requests.
+
+</p>
+
+<p>
+ The Consumer printer.example.com already established a Consumer Key
+ and Consumer Secret with photos.example.net and advertizes its
+ printing services for photos stored on photos.example.net. The
+ Consumer registration is:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>Consumer Key:</dt>
+<dd>
+ <tt>
+ dpf43f3p2l4k3l03
+ </tt>
+
+</dd>
+<dt>Consumer Secret:</dt>
+<dd>
+ <tt>kd94hf93k423kf44</tt>
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p><a name="anchor27"></a><br></p><hr>
+
+<p><a name="rfc.section.A.2"></a></p><h3>Appendix A.2.&nbsp;
+Obtaining a Request Token</h3>
+
+<p>
+ After Jane informs printer.example.com that she would like to print
+ her vacation photo stored at photos.example.net, the printer website
+ tries to access the photo and receives HTTP 401 Unauthorized
+ indicating it is private. The Service Provider includes the following
+ header with the response:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> WWW-Authenticate: OAuth realm="http://photos.example.net/"
+</pre></div><p>
+
+
+</p>
+
+<p>
+ The Consumer sends the following HTTP POST request to the Service
+ Provider:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> https://photos.example.net/request_token?oauth_consumer_key=dpf43f3p2l4k3l03&amp;oauth_signature_method=PLAINTEXT&amp;oauth_signature=kd94hf93k423kf44%26&amp;oauth_timestamp=1191242090&amp;oauth_nonce=hsu94j3884jdopsl&amp;oauth_version=1.0
+</pre></div><p>
+
+
+</p>
+
+<p>
+ The Service Provider checks the signature and replies with an
+ unauthorized Request Token in the body of the HTTP response:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> oauth_token=hh5s93j4hdidpola&amp;oauth_token_secret=hdhd0244k9j7ao03
+</pre></div><p>
+
+
+</p>
+
+<p><a name="anchor28"></a><br></p><hr>
+
+<p><a name="rfc.section.A.3"></a></p><h3>Appendix A.3.&nbsp;
+Requesting User Authorization</h3>
+
+<p>
+ The Consumer redirects Jane’s browser to the Service Provider
+ User Authorization URL to obtain Jane’s approval for accessing
+ her private photos.
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> http://photos.example.net/authorize?oauth_token=hh5s93j4hdidpola&amp;oauth_callback=http%3A%2F%2Fprinter.example.com%2Frequest_token_ready
+</pre></div><p>
+
+
+</p>
+
+<p>
+ The Service Provider asks Jane to sign-in using her username and
+ password and, if successful, asks her if she approves granting
+ printer.example.com access to her private photos. If Jane approves
+ the request, the Service Provider redirects her back to the
+ Consumer’s callback URL:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> http://printer.example.com/request_token_ready?oauth_token=hh5s93j4hdidpola
+</pre></div><p>
+
+
+</p>
+
+<p><a name="anchor29"></a><br></p><hr>
+
+<p><a name="rfc.section.A.4"></a></p><h3>Appendix A.4.&nbsp;
+Obtaining an Access Token</h3>
+
+<p>
+ Now that the Consumer knows Jane approved the Request Token, it
+ asks the Service Provider to exchange it for an Access Token:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> https://photos.example.net/access_token?oauth_consumer_key=dpf43f3p2l4k3l03&amp;oauth_token=hh5s93j4hdidpola&amp;oauth_signature_method=PLAINTEXT&amp;oauth_signature=kd94hf93k423kf44%26hdhd0244k9j7ao03&amp;oauth_timestamp=1191242092&amp;oauth_nonce=dji430splmx33448&amp;oauth_version=1.0
+</pre></div><p>
+
+
+</p>
+
+<p>
+ The Service Provider checks the signature and replies with an
+ Access Token in the body of the HTTP response:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> oauth_token=nnch734d00sl2jdk&amp;oauth_token_secret=pfkkdhi9sl3r4s00
+</pre></div><p>
+
+
+</p>
+
+<p><a name="anchor30"></a><br></p><hr>
+
+<p><a name="rfc.section.A.5"></a></p><h3>Appendix A.5.&nbsp;
+Accessing Protected Resources</h3>
+
+<p>
+ The Consumer is now ready to request the private photo. Since the
+ photo URL is not secure (HTTP), it must use <tt>HMAC-SHA1</tt>.
+
+</p>
+
+<p><a name="sig_base_example"></a><br></p><hr>
+
+<p><a name="rfc.section.A.5.1"></a></p><h3>Appendix A.5.1.&nbsp;
+Generating Signature Base String</h3>
+
+<p>
+ To generate the signature, it first needs to generate the Signature
+ Base String. The request contains the following parameters
+ (<tt>oauth_signature</tt> excluded) which are ordered and concatenated into
+ a normalized string:
+
+ </p>
+<blockquote class="text"><dl>
+<dt>oauth_consumer_key:</dt>
+<dd>
+ <tt>dpf43f3p2l4k3l03</tt>
+
+</dd>
+<dt>oauth_token:</dt>
+<dd>
+ <tt>nnch734d00sl2jdk</tt>
+
+</dd>
+<dt>oauth_signature_method:</dt>
+<dd>
+ <tt>HMAC-SHA1</tt>
+
+</dd>
+<dt>oauth_timestamp:</dt>
+<dd>
+ <tt>1191242096</tt>
+
+</dd>
+<dt>oauth_nonce:</dt>
+<dd>
+ <tt>kllo9940pd9333jh</tt>
+
+</dd>
+<dt>oauth_version:</dt>
+<dd>
+ <tt>1.0</tt>
+
+</dd>
+<dt>file:</dt>
+<dd>
+ <tt>vacation.jpg</tt>
+
+</dd>
+<dt>size:</dt>
+<dd>
+ <tt>original</tt>
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+
+<p>
+ The following inputs are used to generate the Signature Base String:
+
+ </p>
+<ol class="text">
+<li>
+ <tt>GET</tt>
+
+</li>
+<li>
+ <tt>http://photos.example.net/photos</tt>
+
+</li>
+<li>
+ <tt>file=vacation.jpg&amp;oauth_consumer_key=dpf43f3p2l4k3l03&amp;oauth_nonce=kllo9940pd9333jh&amp;oauth_signature_method=HMAC-SHA1&amp;oauth_timestamp=1191242096&amp;oauth_token=nnch734d00sl2jdk&amp;oauth_version=1.0&amp;size=original</tt>
+
+</li>
+</ol><p>
+
+</p>
+
+<p>
+ The Signature Base String is:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> GET&amp;http%3A%2F%2Fphotos.example.net%2Fphotos&amp;file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal
+</pre></div><p>
+
+
+</p>
+
+<p><a name="anchor31"></a><br></p><hr>
+
+<p><a name="rfc.section.A.5.2"></a></p><h3>Appendix A.5.2.&nbsp;
+Calculating Signature Value</h3>
+
+<p>
+ HMAC-SHA1 produces the following <tt>digest</tt> value as a base64-encoded
+ string (using the Signature Base String as <tt>text</tt> and
+ <tt>
+ kd94hf93k423kf44&amp;pfkkdhi9sl3r4s00
+ </tt> as <tt>key</tt>):
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> tR3+Ty81lMeYAr/Fid0kMTYa/WM=
+</pre></div><p>
+
+
+</p>
+
+<p><a name="anchor32"></a><br></p><hr>
+
+<p><a name="rfc.section.A.5.3"></a></p><h3>Appendix A.5.3.&nbsp;
+Requesting Protected Resource</h3>
+
+<p>
+ All together, the Consumer request for the photo is:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> http://photos.example.net/photos?file=vacation.jpg&amp;size=original
+
+ Authorization: OAuth realm="http://photos.example.net/",
+ oauth_consumer_key="dpf43f3p2l4k3l03",
+ oauth_token="nnch734d00sl2jdk",
+ oauth_signature_method="HMAC-SHA1",
+ oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",
+ oauth_timestamp="1191242096",
+ oauth_nonce="kllo9940pd9333jh",
+ oauth_version="1.0"
+</pre></div><p>
+
+
+</p>
+
+<p>
+ And if using query parameters:
+
+ </p>
+<div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> http://photos.example.net/photos?file=vacation.jpg&amp;size=original&amp;oauth_consumer_key=dpf43f3p2l4k3l03&amp;oauth_token=nnch734d00sl2jdk&amp;oauth_signature_method=HMAC-SHA1&amp;oauth_signature=tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D&amp;oauth_timestamp=1191242096&amp;oauth_nonce=kllo9940pd9333jh&amp;oauth_version=1.0
+</pre></div><p>
+
+
+</p>
+
+<p>
+ photos.example.net checks the signature and responds with the
+ requested photo.
+
+</p>
+
+<p><a name="anchor33"></a><br></p><hr>
+
+<p><a name="rfc.section.B"></a></p><h3>Appendix B.&nbsp;
+Security Considerations</h3>
+
+<p><a name="anchor34"></a><br></p><hr>
+
+<p><a name="rfc.section.B.1"></a></p><h3>Appendix B.1.&nbsp;
+Credentials and Token Exchange</h3>
+
+<p>
+ The OAuth specification does not describe any mechanism for protecting
+ Tokens and secrets from eavesdroppers when they are transmitted from
+ the Service Provider to the Consumer in <a class="info" href="#request_grant">Section&nbsp;6.1.2<span> (</span><span class="info">Service Provider Issues an Unauthorized Request Token</span><span>)</span></a>
+ and <a class="info" href="#access_grant">Section&nbsp;6.3.2<span> (</span><span class="info">Service Provider Grants an Access Token</span><span>)</span></a>. Service Providers should ensure
+ that these transmissions are protected using transport-layer mechanisms
+ such as TLS or SSL.
+
+</p>
+
+<p><a name="anchor35"></a><br></p><hr>
+
+<p><a name="rfc.section.B.2"></a></p><h3>Appendix B.2.&nbsp;
+PLAINTEXT Signature Method</h3>
+
+<p>
+ When used with <tt>PLAINTEXT</tt> signatures, the
+ OAuth protocol makes no attempts to protect User credentials from
+ eavesdroppers or man-in-the-middle attacks.
+ The <tt>PLAINTEXT</tt> signature algorithm is only
+ intended to be used in conjunction with a transport-layer security
+ mechanism such as TLS or SSL which does provide such protection.
+ If transport-layer protection is unavailable, the
+ <tt>PLAINTEXT</tt> signature method should not be
+ used.
+
+</p>
+
+<p><a name="anchor36"></a><br></p><hr>
+
+<p><a name="rfc.section.B.3"></a></p><h3>Appendix B.3.&nbsp;
+Confidentiality of Requests</h3>
+
+<p>
+ While OAuth provides a mechanism for verifying the integrity of
+ requests, it provides no guarantee of request confidentiality.
+ Unless further precautions are taken, eavesdroppers will have full
+ access to request content. Service Providers should carefully
+ consider the kinds of data likely to be sent as part of such requests,
+ and should employ transport-layer security mechanisms to protect
+ sensitive resources.
+
+</p>
+
+<p><a name="anchor37"></a><br></p><hr>
+
+<p><a name="rfc.section.B.4"></a></p><h3>Appendix B.4.&nbsp;
+Spoofing by Counterfeit Servers</h3>
+
+<p>
+ OAuth makes no attempt to verify the authenticity of the Service
+ Provider. A hostile party could take advantage of this by intercepting
+ the Consumer’s requests and returning misleading or otherwise incorrect
+ responses. Service providers should consider such attacks when
+ developing services based on OAuth, and should require transport-layer
+ security for any requests where the authenticity of the Service
+ Provider or of request responses is an issue.
+
+</p>
+
+<p><a name="anchor38"></a><br></p><hr>
+
+<p><a name="rfc.section.B.5"></a></p><h3>Appendix B.5.&nbsp;
+Proxying and Caching of Authenticated Content</h3>
+
+<p>
+ The <a class="info" href="#auth_header">HTTP Authorization scheme<span> (</span><span class="info">OAuth HTTP Authorization Scheme</span><span>)</span></a> is
+ optional. However, <a class="info" href="#RFC2616">[RFC2616]<span> (</span><span class="info">Fielding,
+R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P., and T.
+Berners-Lee, “Hypertext Transfer Protocol – HTTP/1.1,” .</span><span>)</span></a> relies on the
+ <tt>Authorization</tt> and
+ <tt>WWW-Authenticate</tt> headers to distinguish
+ authenticated content so that it can be protected. Proxies and
+ caches, in particular, may fail to adequately protect requests not
+ using these headers.
+
+</p>
+
+<p>
+ For example, private authenticated content may be stored in (and thus
+ retrievable from) publicly-accessible caches. Service Providers not
+ using the <a class="info" href="#auth_header">HTTP Authorization scheme<span> (</span><span class="info">OAuth HTTP Authorization Scheme</span><span>)</span></a>
+ should take care to use other mechanisms, such as the
+ <tt>Cache-Control</tt> header, to ensure that
+ authenticated content is protected.
+
+</p>
+
+<p><a name="anchor39"></a><br></p><hr>
+
+<p><a name="rfc.section.B.6"></a></p><h3>Appendix B.6.&nbsp;
+Plaintext Storage of Credentials</h3>
+
+<p>
+ The Consumer Secret and Token Secret function the same way passwords
+ do in traditional authentication systems. In order to compute the
+ signatures used in the non-<tt>PLAINTEXT</tt>
+ methods, the Service Provider must have access to these secrets in
+ plaintext form. This is in contrast, for example, to modern operating
+ systems, which store only a one-way hash of user credentials.
+
+</p>
+
+<p>
+ If an attacker were to gain access to these secrets - or worse, to
+ the Service Provider’s database of all such secrets - he or she would
+ be able to perform any action on behalf of any User. Accordingly, it
+ is critical that Service Providers protect these secrets from
+ unauthorized access.
+
+</p>
+
+<p><a name="anchor40"></a><br></p><hr>
+
+<p><a name="rfc.section.B.7"></a></p><h3>Appendix B.7.&nbsp;
+Secrecy of the Consumer Secret</h3>
+
+<p>
+ In many applications, the Consumer application will be under the
+ control of potentially untrusted parties. For example, if the
+ Consumer is a freely available desktop application, an attacker may
+ be able to download a copy for analysis. In such cases, attackers
+ will be able to recover the Consumer Secret used to authenticate the
+ Consumer to the Service Provider.
+
+</p>
+
+<p>
+ Accordingly, Service Providers should not use the Consumer Secret
+ alone to verify the identity of the Consumer. Where possible, other
+ factors such as IP address should be used as well.
+
+</p>
+
+<p><a name="anchor41"></a><br></p><hr>
+
+<p><a name="rfc.section.B.8"></a></p><h3>Appendix B.8.&nbsp;
+Phishing Attacks</h3>
+
+<p>
+ Wide deployment of OAuth and similar protocols may cause
+ Users to become inured to the practice of being redirected to
+ websites where they are asked to enter their passwords. If Users are
+ not careful to verify the authenticity of these websites before
+ entering their credentials, it will be possible for attackers to
+ exploit this practice to steal Users’ passwords.
+
+</p>
+
+<p>
+ Service Providers should attempt to educate Users about the risks
+ phishing attacks pose, and should provide mechanisms that make it
+ easy for Users to confirm the authenticity of their sites.
+
+</p>
+
+<p><a name="anchor42"></a><br></p><hr>
+
+<p><a name="rfc.section.B.9"></a></p><h3>Appendix B.9.&nbsp;
+Scoping of Access Requests</h3>
+
+<p>
+ By itself, OAuth does not provide any method for scoping the access
+ rights granted to a Consumer. A Consumer either has access to
+ Protected Resources or it doesn’t. Many applications will, however,
+ require greater granularity of access rights. For example, Service
+ Providers may wish to make it possible to grant access to some
+ Protected Resources but not others, or to grant only limited access
+ (such as read-only access) to those Protected Resources.
+
+</p>
+
+<p>
+ When implementing OAuth, Service Providers should consider the types
+ of access Users may wish to grant Consumers, and should provide
+ mechanisms to do so. Service Providers should also take care to
+ ensure that Users understand the access they are granting, as well as
+ any risks that may be involved.
+
+</p>
+
+<p><a name="anchor43"></a><br></p><hr>
+
+<p><a name="rfc.section.B.10"></a></p><h3>Appendix B.10.&nbsp;
+Entropy of Secrets</h3>
+
+<p>
+ Unless a transport-layer security protocol is used, eavesdroppers will
+ have full access to OAuth requests and signatures, and will thus be
+ able to mount offline brute-force attacks to recover the Consumer’s
+ credentials used. Service Providers should be careful to assign Token
+ Secrets and Consumer Secrets which are long enough - and random enough
+ - to resist such attacks for at least the length of time that the
+ secrets are valid.
+
+</p>
+
+<p>
+ For example, if Token Secrets are valid for two weeks, Service
+ Providers should ensure that it is not possible to mount a brute force
+ attack that recovers the Token Secret in less than two weeks. Of
+ course, Service Providers are urged to err on the side of caution,
+ and use the longest secrets reasonable.
+
+</p>
+
+<p>
+ It is equally important that the pseudo-random number generator (PRNG)
+ used to generate these secrets be of sufficiently high quality. Many
+ PRNG implementations generate number sequences that may appear to be
+ random, but which nevertheless exhibit patterns or other weaknesses
+ which make cryptanalysis or brute force attacks easier. Implementors
+ should be careful to use cryptographically secure PRNGs to avoid these
+ problems.
+
+</p>
+
+<p><a name="anchor44"></a><br></p><hr>
+
+<p><a name="rfc.section.B.11"></a></p><h3>Appendix B.11.&nbsp;
+Denial of Service / Resource Exhaustion Attacks</h3>
+
+<p>
+ The OAuth protocol has a number of features which may make resource
+ exhaustion attacks against Service Providers possible. For example,
+ if a Service Provider includes a nontrivial amount of entropy in Token
+ Secrets as recommended above, then an attacker may be able to exhaust
+ the Service Provider’s entropy pool very quickly by repeatedly
+ obtaining Request Tokens from the Service Provider.
+
+</p>
+
+<p>
+ Similarly, OAuth requires Service Providers to track used nonces. If
+ an attacker is able to use many nonces quickly, the resources required
+ to track them may exhaust available capacity. And again, OAuth can
+ require Service Providers to perform potentially expensive computations
+ in order to verify the signature on incoming requests. An attacker may
+ exploit this to perform a denial of service attack by sending a large
+ number of invalid requests to the Service Provider.
+
+</p>
+
+<p>
+ Resource Exhaustion attacks are by no means specific to OAuth. However,
+ OAuth implementors should be careful to consider the additional
+ avenues of attack that OAuth exposes, and design their implementations
+ accordingly. For example, entropy starvation typically results in
+ either a complete denial of service while the system waits for new
+ entropy or else in weak (easily guessable) secrets. When implementing
+ OAuth, Service Providers should consider which of these presents a
+ more serious risk for their application and design accordingly.
+
+</p>
+
+<p><a name="anchor45"></a><br></p><hr>
+
+<p><a name="rfc.section.B.12"></a></p><h3>Appendix B.12.&nbsp;
+Cryptographic Attacks</h3>
+
+<p>
+ SHA-1, the hash algorithm used in <tt>HMAC-SHA1</tt>
+ signatures, has been <a class="info" href="#SHA1">shown<span> (</span><span class="info">De Canniere, C. and C. Rechberger, “Finding SHA-1 Characteristics: General Results and Applications,” .</span><span>)</span></a> [SHA1] to have a number
+ of cryptographic weaknesses that significantly reduce its resistance to
+ collision attacks. Practically speaking, these weaknesses are difficult
+ to exploit, and by themselves do not pose a significant risk to users
+ of OAuth. They may, however, make more efficient attacks possible, and
+ NIST has <a class="info" href="#NIST">announced<span> (</span><span class="info">National
+Institute of Standards and Technolog, NIST., “NIST Brief Comments on
+Recent Cryptanalytic Attacks on Secure Hashing Functions and the
+Continued Security Provided by SHA-1,” .</span><span>)</span></a> [NIST] that it will phase out
+ use of SHA-1 by 2010. Service Providers should take this into account
+ when considering whether SHA-1 provides an adequate level of security
+ for their applications.
+
+</p>
+
+<p><a name="anchor46"></a><br></p><hr>
+
+<p><a name="rfc.section.B.13"></a></p><h3>Appendix B.13.&nbsp;
+Signature Base String Compatibility</h3>
+
+<p>
+ The Signature Base String has been designed to support the signature
+ methods defined in this specification. When designing additional
+ signature methods, the Signature Base String should be evaluated to
+ ensure compatibility with the algorithms used.
+
+</p>
+
+<p>
+ The Signature Base String cannot guarantee the order in which parameters
+ are sent. If parameter ordering is important and affects the result of a
+ request, the Signature Base String will not protect against request
+ manipulation.
+
+</p>
+
+<p><a name="rfc.references1"></a><br></p><hr>
+
+<h3>11.&nbsp;References</h3>
+
+<table border="0" width="99%">
+<tbody><tr><td class="author-text" valign="top"><a name="NIST">[NIST]</a></td>
+<td class="author-text">National Institute of Standards and Technolog, NIST., “<a href="http://csrc.nist.gov/hash_standards_comments.pdf">NIST Brief Comments on Recent Cryptanalytic Attacks on Secure Hashing Functions and the Continued Security Provided by SHA-1</a>.”</td></tr>
+<tr><td class="author-text" valign="top"><a name="RFC2045">[RFC2045]</a></td>
+<td class="author-text">Freed, N. and N. Borenstein, “<a href="http://tools.ietf.org/html/rfc2045">Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</a>,” RFC&nbsp;2045.</td></tr>
+<tr><td class="author-text" valign="top"><a name="RFC2104">[RFC2104]</a></td>
+<td class="author-text">Krawczyk, H., Bellare, M., and R. Canetti, “<a href="http://tools.ietf.org/html/rfc2104">HMAC: Keyed-Hashing for Message Authentication</a>,” RFC&nbsp;2104.</td></tr>
+<tr><td class="author-text" valign="top"><a name="RFC2119">[RFC2119]</a></td>
+<td class="author-text">Bradner, B., “<a href="http://tools.ietf.org/html/rfc2119">Key words for use in RFCs to Indicate Requirement Levels</a>,” RFC&nbsp;2119.</td></tr>
+<tr><td class="author-text" valign="top"><a name="RFC2606">[RFC2606]</a></td>
+<td class="author-text">Eastlake, D. and A. Panitz, “<a href="http://tools.ietf.org/html/rfc2606">Reserved Top Level DNS Names</a>,” RFC&nbsp;2606.</td></tr>
+<tr><td class="author-text" valign="top"><a name="RFC2616">[RFC2616]</a></td>
+<td class="author-text">Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P., and T. Berners-Lee, “<a href="http://tools.ietf.org/html/rfc2616">Hypertext Transfer Protocol – HTTP/1.1</a>,” RFC&nbsp;2616.</td></tr>
+<tr><td class="author-text" valign="top"><a name="RFC2617">[RFC2617]</a></td>
+<td class="author-text">Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen, A., and L. Stewart, “<a href="http://tools.ietf.org/html/rfc2617">HTTP Authentication: Basic and Digest Access Authentication</a>,” RFC&nbsp;2617.</td></tr>
+<tr><td class="author-text" valign="top"><a name="RFC3447">[RFC3447]</a></td>
+<td class="author-text">Jonsson, J. and B. Kaliski, “<a href="http://tools.ietf.org/html/rfc3447">Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography; Specifications Version 2.1</a>,” RFC&nbsp;3447.</td></tr>
+<tr><td class="author-text" valign="top"><a name="RFC3629">[RFC3629]</a></td>
+<td class="author-text">Yergeau, F., “<a href="http://tools.ietf.org/html/rfc3629">UTF-8, a transformation format of Unicode and ISO 10646</a>,” RFC&nbsp;3629.</td></tr>
+<tr><td class="author-text" valign="top"><a name="RFC3986">[RFC3986]</a></td>
+<td class="author-text">Berners-Lee, T., “<a href="http://tools.ietf.org/html/rfc3986">Uniform Resource Identifiers (URI): Generic Syntax</a>,” RFC&nbsp;3986.</td></tr>
+<tr><td class="author-text" valign="top"><a name="SHA1">[SHA1]</a></td>
+<td class="author-text">De Canniere, C. and C. Rechberger, “<a href="http://dx.doi.org/10.1007/11935230_1">Finding SHA-1 Characteristics: General Results and Applications</a>.”</td></tr>
+</tbody></table>
+
+<p><a name="rfc.authors"></a><br></p><hr>
+
+<h3>Author’s Address</h3>
+
+<table border="0" cellpadding="0" cellspacing="0" width="99%">
+<tbody><tr><td class="author-text">&nbsp;</td>
+<td class="author-text">OAuth Core Workgroup</td></tr>
+<tr><td class="author" align="right">Email:&nbsp;</td>
+<td class="author-text"><a href="mailto:spec@oauth.net">spec@oauth.net</a></td></tr>
+</tbody></table>
+<script type="text/javascript">
+ var gaJsHost = (("https:" == document.location.protocol) ?
+ "https://ssl." : "http://www.");
+ document.write(unescape("%3Cscript src='" + gaJsHost +
+ "google-analytics.com/ga.js'
+type='text/javascript'%3E%3C/script%3E"));
+</script>
+<script type="text/javascript">
+ var pageTracker = _gat._getTracker("UA-31771-2");
+ pageTracker._initData();
+ pageTracker._trackPageview();
+</script>
+</body></html> \ No newline at end of file
diff --git a/doc/specs/OAuth Core 1.0_files/diagram.png b/doc/specs/OAuth Core 1.0_files/diagram.png
new file mode 100644
index 0000000..8b5835b
--- /dev/null
+++ b/doc/specs/OAuth Core 1.0_files/diagram.png
Binary files differ
diff --git a/lib/System.Web.Abstractions.dll b/lib/System.Web.Abstractions.dll
new file mode 100644
index 0000000..cd88be0
--- /dev/null
+++ b/lib/System.Web.Abstractions.dll
Binary files differ
diff --git a/lib/System.Web.Mvc.dll b/lib/System.Web.Mvc.dll
new file mode 100644
index 0000000..598cf6f
--- /dev/null
+++ b/lib/System.Web.Mvc.dll
Binary files differ
diff --git a/lib/System.Web.Routing.dll b/lib/System.Web.Routing.dll
new file mode 100644
index 0000000..98ce397
--- /dev/null
+++ b/lib/System.Web.Routing.dll
Binary files differ
diff --git a/readme.txt b/readme.txt
index 24ccdf0..385d3cb 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,7 +1 @@
-This is the future home of YOURLIBNAME.
-
-To customize it for a library:
-1. Find & Replace in Files with case sensitive search:
- YOURLIBNAME -> YourLibrary
-2. Do a dir /s *YOURLIBNAME* in the root of the project and rename all files/directories to *YourLibrary*.
- dir -rec . *YOURLIBNAME* |% { ren $_.fullname $_.name.replace("YOURLIBNAME", "YourLibrary") } \ No newline at end of file
+This is the future home of DotNetOAuth.
diff --git a/src/YOURLIBNAME.Test/.gitignore b/src/DotNetOAuth.Test/.gitignore
index 58b701f..58b701f 100644
--- a/src/YOURLIBNAME.Test/.gitignore
+++ b/src/DotNetOAuth.Test/.gitignore
diff --git a/src/YOURLIBNAME.Test/YOURLIBNAME.Test.csproj b/src/DotNetOAuth.Test/DotNetOAuth.Test.csproj
index 1dd166f..8f28791 100644
--- a/src/YOURLIBNAME.Test/YOURLIBNAME.Test.csproj
+++ b/src/DotNetOAuth.Test/DotNetOAuth.Test.csproj
@@ -7,8 +7,8 @@
<ProjectGuid>{4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>YOURLIBNAME.Test</RootNamespace>
- <AssemblyName>YOURLIBNAME.Test</AssemblyName>
+ <RootNamespace>DotNetOAuth.Test</RootNamespace>
+ <AssemblyName>DotNetOAuth.Test</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
@@ -58,18 +58,30 @@
</Reference>
</ItemGroup>
<ItemGroup>
+ <Compile Include="MessagingUtilitiesTest.cs" />
+ <Compile Include="Messaging\ChannelTests.cs" />
+ <Compile Include="Messaging\DictionaryXmlReaderTests.cs" />
+ <Compile Include="Mocks\TestDirectedMessage.cs" />
+ <Compile Include="Mocks\TestBadChannel.cs" />
+ <Compile Include="OAuthChannelTests.cs" />
+ <Compile Include="Messaging\MessageSerializerTests.cs" />
+ <Compile Include="Mocks\TestChannel.cs" />
+ <Compile Include="Mocks\TestMessage.cs" />
+ <Compile Include="Mocks\TestMessageTypeProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Messaging\ResponseTest.cs" />
+ <Compile Include="ServiceProviderTest.cs" />
<Compile Include="TestBase.cs" />
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\YOURLIBNAME\YOURLIBNAME.csproj">
+ <ProjectReference Include="..\DotNetOAuth\DotNetOAuth.csproj">
<Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project>
- <Name>YOURLIBNAME</Name>
+ <Name>DotNetOAuth</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Logging.config" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
- <Import Project="..\..\tools\YOURLIBNAME.Versioning.targets" />
+ <Import Project="..\..\tools\DotNetOAuth.Versioning.targets" />
</Project> \ No newline at end of file
diff --git a/src/YOURLIBNAME.Test/Logging.config b/src/DotNetOAuth.Test/Logging.config
index a1d675b..a1d675b 100644
--- a/src/YOURLIBNAME.Test/Logging.config
+++ b/src/DotNetOAuth.Test/Logging.config
diff --git a/src/DotNetOAuth.Test/Messaging/ChannelTests.cs b/src/DotNetOAuth.Test/Messaging/ChannelTests.cs
new file mode 100644
index 0000000..a2909c4
--- /dev/null
+++ b/src/DotNetOAuth.Test/Messaging/ChannelTests.cs
@@ -0,0 +1,107 @@
+//-----------------------------------------------------------------------
+// <copyright file="ChannelTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Net;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Test.Mocks;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class ChannelTests : TestBase {
+ private Channel channel;
+
+ [TestInitialize]
+ public override void SetUp() {
+ base.SetUp();
+
+ this.channel = new TestChannel();
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CtorNull() {
+ // This bad channel is deliberately constructed to pass null to
+ // its protected base class' constructor.
+ new TestBadChannel();
+ }
+
+ [TestMethod]
+ public void DequeueIndirectOrResponseMessageReturnsNull() {
+ Assert.IsNull(this.channel.DequeueIndirectOrResponseMessage());
+ }
+
+ [TestMethod]
+ public void ReadFromRequestQueryString() {
+ this.ParameterizedReceiveTest("GET");
+ }
+
+ [TestMethod]
+ public void ReadFromRequestForm() {
+ this.ParameterizedReceiveTest("POST");
+ }
+
+ [TestMethod]
+ public void SendIndirectMessage() {
+ IProtocolMessage message = new TestDirectedMessage {
+ Age = 15,
+ Name = "Andrew",
+ Location = new Uri("http://host/path"),
+ Recipient = new Uri("http://provider/path"),
+ };
+ this.channel.Send(message);
+ Response response = this.channel.DequeueIndirectOrResponseMessage();
+ Assert.AreEqual(HttpStatusCode.Redirect, response.Status);
+ StringAssert.StartsWith(response.Headers[HttpResponseHeader.Location], "http://provider/path");
+ StringAssert.Contains(response.Headers[HttpResponseHeader.Location], "age=15");
+ StringAssert.Contains(response.Headers[HttpResponseHeader.Location], "Name=Andrew");
+ StringAssert.Contains(response.Headers[HttpResponseHeader.Location], "Location=http%3a%2f%2fhost%2fpath");
+ }
+
+ private static HttpRequestInfo CreateHttpRequest(string method, IDictionary<string, string> fields) {
+ string query = MessagingUtilities.CreateQueryString(fields);
+ UriBuilder requestUri = new UriBuilder("http://localhost/path");
+ WebHeaderCollection headers = new WebHeaderCollection();
+ MemoryStream ms = new MemoryStream();
+ if (method == "POST") {
+ headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded");
+ StreamWriter sw = new StreamWriter(ms);
+ sw.Write(query);
+ sw.Flush();
+ ms.Position = 0;
+ } else if (method == "GET") {
+ requestUri.Query = query;
+ } else {
+ throw new ArgumentOutOfRangeException("method", method, "Expected POST or GET");
+ }
+ HttpRequestInfo request = new HttpRequestInfo {
+ HttpMethod = method,
+ Url = requestUri.Uri,
+ Headers = headers,
+ InputStream = ms,
+ };
+
+ return request;
+ }
+
+ private void ParameterizedReceiveTest(string method) {
+ var fields = new Dictionary<string, string> {
+ { "age", "15" },
+ { "Name", "Andrew" },
+ { "Location", "http://hostb/pathB" },
+ };
+ IProtocolMessage requestMessage = this.channel.ReadFromRequest(CreateHttpRequest(method, fields));
+ Assert.IsNotNull(requestMessage);
+ Assert.IsInstanceOfType(requestMessage, typeof(TestMessage));
+ TestMessage testMessage = (TestMessage)requestMessage;
+ Assert.AreEqual(15, testMessage.Age);
+ Assert.AreEqual("Andrew", testMessage.Name);
+ Assert.AreEqual("http://hostb/pathB", testMessage.Location.AbsoluteUri);
+ }
+ }
+}
diff --git a/src/DotNetOAuth.Test/Messaging/DictionaryXmlReaderTests.cs b/src/DotNetOAuth.Test/Messaging/DictionaryXmlReaderTests.cs
new file mode 100644
index 0000000..3dc3238
--- /dev/null
+++ b/src/DotNetOAuth.Test/Messaging/DictionaryXmlReaderTests.cs
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------
+// <copyright file="DictionaryXmlReaderTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Xml.Linq;
+ using DotNetOAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class DictionaryXmlReaderTests : TestBase {
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CreateWithNullRootElement() {
+ DictionaryXmlReader.Create(null, new Dictionary<string, string>());
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CreateWithNullFields() {
+ DictionaryXmlReader.Create(XName.Get("name", "ns"), null);
+ }
+ }
+}
diff --git a/src/DotNetOAuth.Test/Messaging/MessageSerializerTests.cs b/src/DotNetOAuth.Test/Messaging/MessageSerializerTests.cs
new file mode 100644
index 0000000..5cb5375
--- /dev/null
+++ b/src/DotNetOAuth.Test/Messaging/MessageSerializerTests.cs
@@ -0,0 +1,142 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessageSerializerTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using DotNetOAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ /// <summary>
+ /// Tests for the <see cref="MessageSerializer"/> class.
+ /// </summary>
+ [TestClass()]
+ public class MessageSerializerTests : TestBase {
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void SerializeNull() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ serializer.Serialize(null);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void SerializeNullFields() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ serializer.Serialize(null, new Mocks.TestMessage());
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void SerializeNullMessage() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ serializer.Serialize(new Dictionary<string, string>(), null);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void GetInvalidMessageType() {
+ MessageSerializer.Get(typeof(string));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void GetNullType() {
+ MessageSerializer.Get(null);
+ }
+
+ [TestMethod]
+ public void GetReturnsSameSerializerTwice() {
+ Assert.AreSame(MessageSerializer.Get(typeof(Mocks.TestMessage)), MessageSerializer.Get(typeof(Mocks.TestMessage)));
+ }
+
+ [TestMethod, ExpectedException(typeof(ProtocolException))]
+ public void SerializeInvalidMessage() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ Dictionary<string, string> fields = new Dictionary<string, string>(StringComparer.Ordinal);
+ Mocks.TestMessage message = new Mocks.TestMessage();
+ message.EmptyMember = "invalidvalue";
+ serializer.Serialize(message);
+ }
+
+ [TestMethod()]
+ public void SerializeTest() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ var message = new Mocks.TestMessage { Age = 15, Name = "Andrew", Location = new Uri("http://localhost") };
+ IDictionary<string, string> actual = serializer.Serialize(message);
+ Assert.AreEqual(3, actual.Count);
+
+ // Test case sensitivity of generated dictionary
+ Assert.IsFalse(actual.ContainsKey("Age"));
+ Assert.IsTrue(actual.ContainsKey("age"));
+
+ // Test contents of dictionary
+ Assert.AreEqual("15", actual["age"]);
+ Assert.AreEqual("Andrew", actual["Name"]);
+ Assert.AreEqual("http://localhost/", actual["Location"]);
+ Assert.IsFalse(actual.ContainsKey("EmptyMember"));
+ }
+
+ [TestMethod]
+ public void SerializeToExistingDictionary() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ var message = new Mocks.TestMessage { Age = 15, Name = "Andrew" };
+ var fields = new Dictionary<string, string>();
+ fields["someExtraField"] = "someValue";
+ serializer.Serialize(fields, message);
+ Assert.AreEqual(3, fields.Count);
+ Assert.AreEqual("15", fields["age"]);
+ Assert.AreEqual("Andrew", fields["Name"]);
+ Assert.AreEqual("someValue", fields["someExtraField"]);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void DeserializeNull() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ serializer.Deserialize(null);
+ }
+
+ [TestMethod()]
+ public void DeserializeSimple() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ Dictionary<string, string> fields = new Dictionary<string, string>(StringComparer.Ordinal);
+ // We deliberately do this OUT of alphabetical order (caps would go first),
+ // since DataContractSerializer demands things to be IN alphabetical order.
+ fields["age"] = "15";
+ fields["Name"] = "Andrew";
+ var actual = (Mocks.TestMessage)serializer.Deserialize(fields);
+ Assert.AreEqual(15, actual.Age);
+ Assert.AreEqual("Andrew", actual.Name);
+ Assert.IsNull(actual.EmptyMember);
+ }
+
+ [TestMethod]
+ public void DeserializeWithExtraFields() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ Dictionary<string, string> fields = new Dictionary<string, string>(StringComparer.Ordinal);
+ fields["age"] = "15";
+ fields["Name"] = "Andrew";
+ // Add some field that is not recognized by the class. This simulates a querystring with
+ // more parameters than are actually interesting to the protocol message.
+ fields["someExtraField"] = "asdf";
+ var actual = (Mocks.TestMessage)serializer.Deserialize(fields);
+ Assert.AreEqual(15, actual.Age);
+ Assert.AreEqual("Andrew", actual.Name);
+ Assert.IsNull(actual.EmptyMember);
+ }
+
+ [TestMethod, ExpectedException(typeof(ProtocolException))]
+ public void DeserializeEmpty() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ var fields = new Dictionary<string, string>(StringComparer.Ordinal);
+ serializer.Deserialize(fields);
+ }
+
+ [TestMethod, ExpectedException(typeof(ProtocolException))]
+ public void DeserializeInvalidMessage() {
+ var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage));
+ Dictionary<string, string> fields = new Dictionary<string, string>(StringComparer.Ordinal);
+ // Set an disallowed value.
+ fields["age"] = "-1";
+ serializer.Deserialize(fields);
+ }
+ }
+}
diff --git a/src/DotNetOAuth.Test/Messaging/ResponseTest.cs b/src/DotNetOAuth.Test/Messaging/ResponseTest.cs
new file mode 100644
index 0000000..c1c7dff
--- /dev/null
+++ b/src/DotNetOAuth.Test/Messaging/ResponseTest.cs
@@ -0,0 +1,19 @@
+//-----------------------------------------------------------------------
+// <copyright file="ResponseTest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test.Messaging {
+ using System;
+ using DotNetOAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class ResponseTest : TestBase {
+ [TestMethod, ExpectedException(typeof(InvalidOperationException))]
+ public void SendWithoutAspNetContext() {
+ new Response().Send();
+ }
+ }
+}
diff --git a/src/DotNetOAuth.Test/MessagingUtilitiesTest.cs b/src/DotNetOAuth.Test/MessagingUtilitiesTest.cs
new file mode 100644
index 0000000..cc3e552
--- /dev/null
+++ b/src/DotNetOAuth.Test/MessagingUtilitiesTest.cs
@@ -0,0 +1,99 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessagingUtilitiesTest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.IO;
+ using System.Net;
+ using System.Web;
+ using DotNetOAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class MessagingUtilitiesTest : TestBase {
+ [TestMethod]
+ public void CreateQueryString() {
+ var args = new Dictionary<string, string>();
+ args.Add("a", "b");
+ args.Add("c/d", "e/f");
+ Assert.AreEqual("a=b&c%2fd=e%2ff", MessagingUtilities.CreateQueryString(args));
+ }
+
+ [TestMethod]
+ public void CreateQueryStringEmptyCollection() {
+ Assert.AreEqual(0, MessagingUtilities.CreateQueryString(new Dictionary<string, string>()).Length);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void CreateQueryStringNullDictionary() {
+ MessagingUtilities.CreateQueryString(null);
+ }
+
+ [TestMethod]
+ public void AppendQueryArgs() {
+ UriBuilder uri = new UriBuilder("http://baseline.org/page");
+ var args = new Dictionary<string, string>();
+ args.Add("a", "b");
+ args.Add("c/d", "e/f");
+ MessagingUtilities.AppendQueryArgs(uri, args);
+ Assert.AreEqual("http://baseline.org/page?a=b&c%2fd=e%2ff", uri.Uri.AbsoluteUri);
+ args.Clear();
+ args.Add("g", "h");
+ MessagingUtilities.AppendQueryArgs(uri, args);
+ Assert.AreEqual("http://baseline.org/page?a=b&c%2fd=e%2ff&g=h", uri.Uri.AbsoluteUri);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void AppendQueryArgsNullUriBuilder() {
+ MessagingUtilities.AppendQueryArgs(null, new Dictionary<string, string>());
+ }
+
+ [TestMethod]
+ public void AppendQueryArgsNullDictionary() {
+ MessagingUtilities.AppendQueryArgs(new UriBuilder(), null);
+ }
+
+ [TestMethod]
+ public void ToDictionary() {
+ NameValueCollection nvc = new NameValueCollection();
+ nvc["a"] = "b";
+ nvc["c"] = "d";
+ Dictionary<string, string> actual = MessagingUtilities.ToDictionary(nvc);
+ Assert.AreEqual(nvc.Count, actual.Count);
+ Assert.AreEqual(nvc["a"], actual["a"]);
+ Assert.AreEqual(nvc["c"], actual["c"]);
+ }
+
+ [TestMethod]
+ public void ToDictionaryNull() {
+ Assert.IsNull(MessagingUtilities.ToDictionary(null));
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void ApplyHeadersToResponseNullResponse() {
+ MessagingUtilities.ApplyHeadersToResponse(new WebHeaderCollection(), null);
+ }
+
+ [TestMethod, ExpectedException(typeof(ArgumentNullException))]
+ public void ApplyHeadersToResponseNullHeaders() {
+ MessagingUtilities.ApplyHeadersToResponse(null, new HttpResponse(new StringWriter()));
+ }
+
+ [TestMethod]
+ public void ApplyHeadersToResponse() {
+ var headers = new WebHeaderCollection();
+ headers[HttpResponseHeader.ContentType] = "application/binary";
+
+ var response = new HttpResponse(new StringWriter());
+ MessagingUtilities.ApplyHeadersToResponse(headers, response);
+
+ Assert.AreEqual(headers[HttpResponseHeader.ContentType], response.ContentType);
+ }
+ }
+}
diff --git a/src/DotNetOAuth.Test/Mocks/TestBadChannel.cs b/src/DotNetOAuth.Test/Mocks/TestBadChannel.cs
new file mode 100644
index 0000000..20fe03a
--- /dev/null
+++ b/src/DotNetOAuth.Test/Mocks/TestBadChannel.cs
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestBadChannel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test.Mocks {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// A Channel derived type that passes null to the protected constructor.
+ /// </summary>
+ internal class TestBadChannel : Channel {
+ internal TestBadChannel()
+ : base(null) {
+ }
+
+ protected internal override IProtocolMessage Request(IDirectedProtocolMessage request) {
+ throw new NotImplementedException();
+ }
+
+ protected internal override IProtocolMessage ReadFromResponse(System.IO.Stream responseStream) {
+ throw new NotImplementedException();
+ }
+
+ protected override void SendDirectMessageResponse(IProtocolMessage response) {
+ throw new NotImplementedException();
+ }
+
+ protected override void ReportErrorToUser(ProtocolException exception) {
+ throw new NotImplementedException();
+ }
+
+ protected override void ReportErrorAsDirectResponse(ProtocolException exception) {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/DotNetOAuth.Test/Mocks/TestChannel.cs b/src/DotNetOAuth.Test/Mocks/TestChannel.cs
new file mode 100644
index 0000000..1920a38
--- /dev/null
+++ b/src/DotNetOAuth.Test/Mocks/TestChannel.cs
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestChannel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test.Mocks {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+
+ internal class TestChannel : Channel {
+ internal TestChannel()
+ : base(new TestMessageTypeProvider()) {
+ }
+
+ protected internal override IProtocolMessage Request(IDirectedProtocolMessage request) {
+ throw new NotImplementedException();
+ }
+
+ protected internal override IProtocolMessage ReadFromResponse(System.IO.Stream responseStream) {
+ throw new NotImplementedException();
+ }
+
+ protected override void SendDirectMessageResponse(IProtocolMessage response) {
+ throw new NotImplementedException();
+ }
+
+ protected override void ReportErrorToUser(ProtocolException exception) {
+ throw new NotImplementedException();
+ }
+
+ protected override void ReportErrorAsDirectResponse(ProtocolException exception) {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/DotNetOAuth.Test/Mocks/TestDirectedMessage.cs b/src/DotNetOAuth.Test/Mocks/TestDirectedMessage.cs
new file mode 100644
index 0000000..6972624
--- /dev/null
+++ b/src/DotNetOAuth.Test/Mocks/TestDirectedMessage.cs
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestDirectedMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test.Mocks {
+ using System;
+ using System.Runtime.Serialization;
+ using DotNetOAuth.Messaging;
+
+ [DataContract(Namespace = Protocol.DataContractNamespaceV10)]
+ internal class TestDirectedMessage : IDirectedProtocolMessage {
+ [DataMember(Name = "age", IsRequired = true)]
+ public int Age { get; set; }
+ [DataMember]
+ public string Name { get; set; }
+ [DataMember]
+ public string EmptyMember { get; set; }
+ [DataMember]
+ public Uri Location { get; set; }
+
+ #region IDirectedProtocolMessage Members
+
+ public Uri Recipient { get; internal set; }
+
+ #endregion
+
+ #region IProtocolMessage Members
+
+ Protocol IProtocolMessage.Protocol {
+ get { return Protocol.V10; }
+ }
+
+ MessageTransport IProtocolMessage.Transport {
+ get { return MessageTransport.Direct; }
+ }
+
+ void IProtocolMessage.EnsureValidMessage() {
+ if (this.EmptyMember != null || this.Age < 0) {
+ throw new ProtocolException();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOAuth.Test/Mocks/TestMessage.cs b/src/DotNetOAuth.Test/Mocks/TestMessage.cs
new file mode 100644
index 0000000..8c47717
--- /dev/null
+++ b/src/DotNetOAuth.Test/Mocks/TestMessage.cs
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test.Mocks {
+ using System;
+ using System.Runtime.Serialization;
+ using DotNetOAuth.Messaging;
+
+ [DataContract(Namespace = Protocol.DataContractNamespaceV10)]
+ internal class TestMessage : IProtocolMessage {
+ [DataMember(Name = "age", IsRequired = true)]
+ public int Age { get; set; }
+ [DataMember]
+ public string Name { get; set; }
+ [DataMember]
+ public string EmptyMember { get; set; }
+ [DataMember]
+ public Uri Location { get; set; }
+
+ #region IProtocolMessage Members
+
+ Protocol IProtocolMessage.Protocol {
+ get { return Protocol.V10; }
+ }
+
+ MessageTransport IProtocolMessage.Transport {
+ get { return MessageTransport.Direct; }
+ }
+
+ void IProtocolMessage.EnsureValidMessage() {
+ if (this.EmptyMember != null || this.Age < 0) {
+ throw new ProtocolException();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOAuth.Test/Mocks/TestMessageTypeProvider.cs b/src/DotNetOAuth.Test/Mocks/TestMessageTypeProvider.cs
new file mode 100644
index 0000000..647a09f
--- /dev/null
+++ b/src/DotNetOAuth.Test/Mocks/TestMessageTypeProvider.cs
@@ -0,0 +1,31 @@
+//-----------------------------------------------------------------------
+// <copyright file="TestMessageTypeProvider.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test.Mocks {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+
+ internal class TestMessageTypeProvider : IMessageTypeProvider {
+ #region IMessageTypeProvider Members
+
+ public Type GetRequestMessageType(IDictionary<string, string> fields) {
+ if (fields.ContainsKey("age")) {
+ return typeof(TestMessage);
+ } else {
+ return null;
+ }
+ }
+
+ public Type GetResponseMessageType(IProtocolMessage request, IDictionary<string, string> fields) {
+ return this.GetRequestMessageType(fields);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOAuth.Test/OAuthChannelTests.cs b/src/DotNetOAuth.Test/OAuthChannelTests.cs
new file mode 100644
index 0000000..1099dc6
--- /dev/null
+++ b/src/DotNetOAuth.Test/OAuthChannelTests.cs
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthChannelTests.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class OAuthChannelTests : TestBase {
+ private Channel channel;
+
+ [TestInitialize]
+ public override void SetUp() {
+ base.SetUp();
+
+ this.channel = new OAuthChannel();
+ }
+
+ [TestMethod, Ignore]
+ public void ReadFromRequestAuthorization() {
+ }
+
+ internal static string CreateAuthorizationHeader(IDictionary<string, string> fields) {
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ StringBuilder authorization = new StringBuilder();
+ authorization.Append("OAuth ");
+ foreach (var pair in fields) {
+ string key = Uri.EscapeDataString(pair.Key);
+ string value = Uri.EscapeDataString(pair.Value);
+ authorization.Append(key);
+ authorization.Append("=\"");
+ authorization.Append(value);
+ authorization.Append("\",");
+ }
+ authorization.Length--; // remove trailing comma
+
+ return authorization.ToString();
+ }
+ }
+}
diff --git a/src/YOURLIBNAME.Test/Properties/AssemblyInfo.cs b/src/DotNetOAuth.Test/Properties/AssemblyInfo.cs
index c37288a..2495cc7 100644
--- a/src/YOURLIBNAME.Test/Properties/AssemblyInfo.cs
+++ b/src/DotNetOAuth.Test/Properties/AssemblyInfo.cs
@@ -3,6 +3,7 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
+
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -10,11 +11,11 @@ using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("YOURLIBNAME.Test")]
+[assembly: AssemblyTitle("DotNetOAuth.Test")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("YOURLIBNAME.Test")]
+[assembly: AssemblyProduct("DotNetOAuth.Test")]
[assembly: AssemblyCopyright("Copyright © 2008")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/src/DotNetOAuth.Test/ServiceProviderTest.cs b/src/DotNetOAuth.Test/ServiceProviderTest.cs
new file mode 100644
index 0000000..d4bced7
--- /dev/null
+++ b/src/DotNetOAuth.Test/ServiceProviderTest.cs
@@ -0,0 +1,65 @@
+//-----------------------------------------------------------------------
+// <copyright file="ServiceProviderTest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test {
+ using System;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ /// <summary>
+ /// Tests for the <see cref="ServiceProvider"/> class.
+ /// </summary>
+ [TestClass]
+ public class ServiceProviderTest : TestBase {
+ /// <summary>
+ /// A test for UserAuthorizationUri
+ /// </summary>
+ [TestMethod]
+ public void UserAuthorizationUriTest() {
+ ServiceProvider target = new ServiceProvider();
+ Uri expected = new Uri("http://localhost/authorization");
+ Uri actual;
+ target.UserAuthorizationUri = expected;
+ actual = target.UserAuthorizationUri;
+ Assert.AreEqual(expected, actual);
+ }
+
+ /// <summary>
+ /// A test for RequestTokenUri
+ /// </summary>
+ [TestMethod()]
+ public void RequestTokenUriTest() {
+ ServiceProvider target = new ServiceProvider();
+ Uri expected = new Uri("http://localhost/requesttoken");
+ Uri actual;
+ target.RequestTokenUri = expected;
+ actual = target.RequestTokenUri;
+ Assert.AreEqual(expected, actual);
+ }
+
+ /// <summary>
+ /// Verifies that oauth parameters are not allowed in <see cref="ServiceProvider.RequestTokenUri"/>,
+ /// per section OAuth 1.0 section 4.1.
+ /// </summary>
+ [TestMethod, ExpectedException(typeof(ArgumentException))]
+ public void RequestTokenUriWithOAuthParametersTest() {
+ ServiceProvider target = new ServiceProvider();
+ target.RequestTokenUri = new Uri("http://localhost/requesttoken?oauth_token=something");
+ }
+
+ /// <summary>
+ /// A test for AccessTokenUri
+ /// </summary>
+ [TestMethod()]
+ public void AccessTokenUriTest() {
+ ServiceProvider target = new ServiceProvider();
+ Uri expected = new Uri("http://localhost/accesstoken");
+ Uri actual;
+ target.AccessTokenUri = expected;
+ actual = target.AccessTokenUri;
+ Assert.AreEqual(expected, actual);
+ }
+ }
+}
diff --git a/src/DotNetOAuth.Test/Settings.StyleCop b/src/DotNetOAuth.Test/Settings.StyleCop
new file mode 100644
index 0000000..100fae5
--- /dev/null
+++ b/src/DotNetOAuth.Test/Settings.StyleCop
@@ -0,0 +1,24 @@
+<StyleCopSettings Version="4.3">
+ <Analyzers>
+ <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.DocumentationRules">
+ <Rules>
+ <Rule Name="ElementsMustBeDocumented">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ </Rules>
+ <AnalyzerSettings />
+ </Analyzer>
+ <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.LayoutRules">
+ <Rules>
+ <Rule Name="SingleLineCommentMustBePrecededByBlankLine">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ </Rules>
+ <AnalyzerSettings />
+ </Analyzer>
+ </Analyzers>
+</StyleCopSettings> \ No newline at end of file
diff --git a/src/YOURLIBNAME.Test/TestBase.cs b/src/DotNetOAuth.Test/TestBase.cs
index 5a9c94f..e41b01c 100644
--- a/src/YOURLIBNAME.Test/TestBase.cs
+++ b/src/DotNetOAuth.Test/TestBase.cs
@@ -3,7 +3,7 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
-namespace YOURLIBNAME.Test {
+namespace DotNetOAuth.Test {
using System.Reflection;
using log4net;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -15,7 +15,7 @@ namespace YOURLIBNAME.Test {
/// <summary>
/// The logger that tests should use.
/// </summary>
- internal static readonly ILog Logger = LogManager.GetLogger("YOURLIBNAME.Test");
+ internal static readonly ILog Logger = LogManager.GetLogger("DotNetOAuth.Test");
/// <summary>
/// Gets or sets the test context which provides
@@ -28,7 +28,7 @@ namespace YOURLIBNAME.Test {
/// </summary>
[TestInitialize]
public virtual void SetUp() {
- log4net.Config.XmlConfigurator.Configure(Assembly.GetExecutingAssembly().GetManifestResourceStream("YOURLIBNAME.Test.Logging.config"));
+ log4net.Config.XmlConfigurator.Configure(Assembly.GetExecutingAssembly().GetManifestResourceStream("DotNetOAuth.Test.Logging.config"));
}
/// <summary>
diff --git a/src/YOURLIBNAME.sln b/src/DotNetOAuth.sln
index 007090f..d4e2360 100644
--- a/src/YOURLIBNAME.sln
+++ b/src/DotNetOAuth.sln
@@ -1,19 +1,19 @@

Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YOURLIBNAME", "YOURLIBNAME\YOURLIBNAME.csproj", "{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOAuth", "DotNetOAuth\DotNetOAuth.csproj", "{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YOURLIBNAME.Test", "YOURLIBNAME.Test\YOURLIBNAME.Test.csproj", "{4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOAuth.Test", "DotNetOAuth.Test\DotNetOAuth.Test.csproj", "{4376ECC9-C346-4A99-B13C-FA93C0FBD2C9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{20B5E173-C3C4-49F8-BD25-E69044075B4D}"
ProjectSection(SolutionItems) = preProject
- YOURLIBNAME.vsmdi = YOURLIBNAME.vsmdi
+ DotNetOAuth.vsmdi = DotNetOAuth.vsmdi
LocalTestRun.testrunconfig = LocalTestRun.testrunconfig
EndProjectSection
EndProject
Global
GlobalSection(TestCaseManagementSettings) = postSolution
- CategoryFile = YOURLIBNAME.vsmdi
+ CategoryFile = DotNetOAuth.vsmdi
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/src/YOURLIBNAME.vsmdi b/src/DotNetOAuth.vsmdi
index 027090d..027090d 100644
--- a/src/YOURLIBNAME.vsmdi
+++ b/src/DotNetOAuth.vsmdi
diff --git a/src/YOURLIBNAME/.gitignore b/src/DotNetOAuth/.gitignore
index 2a08796..31c006d 100644
--- a/src/YOURLIBNAME/.gitignore
+++ b/src/DotNetOAuth/.gitignore
@@ -1,3 +1,4 @@
+StyleCop.Cache
*.user
bin
obj
diff --git a/src/DotNetOAuth/ClassDiagram.cd b/src/DotNetOAuth/ClassDiagram.cd
new file mode 100644
index 0000000..1219a22
--- /dev/null
+++ b/src/DotNetOAuth/ClassDiagram.cd
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ClassDiagram MajorVersion="1" MinorVersion="1">
+ <Class Name="DotNetOAuth.ServiceProvider">
+ <Position X="0.5" Y="0.5" Width="2" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAAAAAAAAACAAAACAAAAAAAAAAAAABAAAAAQ=</HashCode>
+ <FileName>ServiceProvider.cs</FileName>
+ </TypeIdentifier>
+ </Class>
+ <Class Name="DotNetOAuth.Consumer">
+ <Position X="2.75" Y="0.5" Width="1.75" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>Consumer.cs</FileName>
+ </TypeIdentifier>
+ </Class>
+ <Class Name="DotNetOAuth.DirectMessageChannel">
+ <Position X="5" Y="2.75" Width="2" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>DirectMessageChannel.cs</FileName>
+ </TypeIdentifier>
+ </Class>
+ <Class Name="DotNetOAuth.IndirectMessageEncoder">
+ <Position X="5" Y="4" Width="2" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>IndirectMessageEncoder.cs</FileName>
+ </TypeIdentifier>
+ </Class>
+ <Class Name="DotNetOAuth.IndirectMessage" Collapsed="true">
+ <Position X="5" Y="5.25" Width="2" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAEAABAAAAAAEABAAAACAAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>IndirectMessage.cs</FileName>
+ </TypeIdentifier>
+ </Class>
+ <Class Name="DotNetOAuth.ProtocolMessageSerializer&lt;T&gt;">
+ <Position X="5" Y="0.5" Width="2.25" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAIAAAAAAAAAAAAAAEACBAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>ProtocolMessageSerializer.cs</FileName>
+ </TypeIdentifier>
+ </Class>
+ <Interface Name="DotNetOAuth.IProtocolMessage">
+ <Position X="2.75" Y="1.5" Width="1.75" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAQAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>IProtocolMessage.cs</FileName>
+ </TypeIdentifier>
+ </Interface>
+ <Interface Name="DotNetOAuth.IProtocolMessageRequest">
+ <Position X="2.75" Y="3.25" Width="1.75" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>IProtocolMessageRequest.cs</FileName>
+ </TypeIdentifier>
+ </Interface>
+ <Enum Name="DotNetOAuth.MessageScheme">
+ <Position X="0.5" Y="2.75" Width="2" />
+ <TypeIdentifier>
+ <HashCode>AQAAAAAAAAAAAAIAAAAAAACAAAAAAAgAAAAAAAAAAAA=</HashCode>
+ <FileName>MessageScheme.cs</FileName>
+ </TypeIdentifier>
+ </Enum>
+ <Enum Name="DotNetOAuth.MessageTransport">
+ <Position X="0.5" Y="4.5" Width="1.75" />
+ <TypeIdentifier>
+ <HashCode>AAACAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>MessageTransport.cs</FileName>
+ </TypeIdentifier>
+ </Enum>
+ <Font Name="Segoe UI" Size="9" />
+</ClassDiagram> \ No newline at end of file
diff --git a/src/DotNetOAuth/Consumer.cs b/src/DotNetOAuth/Consumer.cs
new file mode 100644
index 0000000..1a73d5f
--- /dev/null
+++ b/src/DotNetOAuth/Consumer.cs
@@ -0,0 +1,13 @@
+//-----------------------------------------------------------------------
+// <copyright file="Consumer.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth {
+ /// <summary>
+ /// A website or application that uses OAuth to access the Service Provider on behalf of the User.
+ /// </summary>
+ internal class Consumer {
+ }
+}
diff --git a/src/YOURLIBNAME/YOURLIBNAME.csproj b/src/DotNetOAuth/DotNetOAuth.csproj
index dd8721c..4db5eb0 100644
--- a/src/YOURLIBNAME/YOURLIBNAME.csproj
+++ b/src/DotNetOAuth/DotNetOAuth.csproj
@@ -8,8 +8,8 @@
<ProjectGuid>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>YOURLIBNAME</RootNamespace>
- <AssemblyName>YOURLIBNAME</AssemblyName>
+ <RootNamespace>DotNetOAuth</RootNamespace>
+ <AssemblyName>DotNetOAuth</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
@@ -22,8 +22,8 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
- <DocumentationFile>..\..\bin\debug\YOURLIBNAME.xml</DocumentationFile>
- <RunCodeAnalysis>true</RunCodeAnalysis>
+ <DocumentationFile>..\..\bin\debug\DotNetOAuth.xml</DocumentationFile>
+ <RunCodeAnalysis>false</RunCodeAnalysis>
<CodeAnalysisRules>-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055</CodeAnalysisRules>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@@ -34,7 +34,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
- <DocumentationFile>..\..\bin\debug\YOURLIBNAME.xml</DocumentationFile>
+ <DocumentationFile>..\..\bin\debug\DotNetOAuth.xml</DocumentationFile>
<RunCodeAnalysis>true</RunCodeAnalysis>
<CodeAnalysisRules>-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055</CodeAnalysisRules>
</PropertyGroup>
@@ -66,14 +66,57 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="Consumer.cs" />
+ <Compile Include="Messaging\DictionaryXmlReader.cs" />
+ <Compile Include="Messaging\DictionaryXmlWriter.cs" />
+ <Compile Include="Messaging\Channel.cs" />
+ <Compile Include="Messaging\HttpRequestInfo.cs" />
+ <Compile Include="Messaging\IDirectedProtocolMessage.cs" />
+ <Compile Include="Messaging\IMessageTypeProvider.cs" />
+ <Compile Include="Messaging\MessagingStrings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>MessagingStrings.resx</DependentUpon>
+ </Compile>
+ <Compile Include="Messaging\MessagingUtilities.cs" />
+ <Compile Include="OAuthChannel.cs" />
+ <Compile Include="Messaging\Response.cs" />
+ <Compile Include="Messaging\IProtocolMessage.cs" />
<Compile Include="Logger.cs" />
<Compile Include="Loggers\ILog.cs" />
<Compile Include="Loggers\Log4NetLogger.cs" />
<Compile Include="Loggers\NoOpLogger.cs" />
<Compile Include="Loggers\TraceLogger.cs" />
+ <Compile Include="Messaging\MessageScheme.cs" />
+ <Compile Include="Messaging\MessageTransport.cs" />
+ <Compile Include="OAuthMessageTypeProvider.cs" />
+ <Compile Include="ProtocolException.cs" />
+ <Compile Include="Messaging\MessageSerializer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Util.cs" />
+ <Compile Include="Protocol.cs" />
+ <Compile Include="ServiceProvider.cs" />
+ <Compile Include="Strings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Strings.resx</DependentUpon>
+ </Compile>
+ <Compile Include="UriUtil.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="ClassDiagram.cd" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Messaging\MessagingStrings.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>MessagingStrings.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <EmbeddedResource Include="Strings.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Strings.Designer.cs</LastGenOutput>
+ <SubType>Designer</SubType>
+ </EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <Import Project="..\..\tools\YOURLIBNAME.Versioning.targets" />
+ <Import Project="..\..\tools\DotNetOAuth.Versioning.targets" />
</Project> \ No newline at end of file
diff --git a/src/YOURLIBNAME/Logger.cs b/src/DotNetOAuth/Logger.cs
index f63e8f3..96357a0 100644
--- a/src/YOURLIBNAME/Logger.cs
+++ b/src/DotNetOAuth/Logger.cs
@@ -4,14 +4,14 @@
// </copyright>
//-----------------------------------------------------------------------
-namespace YOURLIBNAME {
+namespace DotNetOAuth {
using System;
using System.Globalization;
+ using DotNetOAuth.Loggers;
using log4net.Core;
- using YOURLIBNAME.Loggers;
/// <summary>
- /// A general logger for the entire YOURLIBNAME library.
+ /// A general logger for the entire DotNetOAuth library.
/// </summary>
/// <remarks>
/// Because this logger is intended for use with non-localized strings, the
@@ -23,7 +23,7 @@ namespace YOURLIBNAME {
/// The <see cref="ILog"/> instance that is to be used
/// by this static Logger for the duration of the appdomain.
/// </summary>
- private static ILog facade = Create("YOURLIBNAME");
+ private static ILog facade = Create("DotNetOAuth");
#region ILog Members
//// Although this static class doesn't literally implement the ILog interface,
diff --git a/src/YOURLIBNAME/Loggers/ILog.cs b/src/DotNetOAuth/Loggers/ILog.cs
index 2f5fa82..51e5570 100644
--- a/src/YOURLIBNAME/Loggers/ILog.cs
+++ b/src/DotNetOAuth/Loggers/ILog.cs
@@ -23,7 +23,7 @@
// hosting web site chooses not to deploy log4net.dll along with
// dotnetopenid.dll.
-namespace YOURLIBNAME.Loggers
+namespace DotNetOAuth.Loggers
{
using System;
using System.Reflection;
diff --git a/src/YOURLIBNAME/Loggers/Log4NetLogger.cs b/src/DotNetOAuth/Loggers/Log4NetLogger.cs
index efd5466..49fd5de 100644
--- a/src/YOURLIBNAME/Loggers/Log4NetLogger.cs
+++ b/src/DotNetOAuth/Loggers/Log4NetLogger.cs
@@ -1,6 +1,6 @@
// <auto-generated />
-namespace YOURLIBNAME.Loggers {
+namespace DotNetOAuth.Loggers {
using System;
using System.Globalization;
using System.IO;
diff --git a/src/YOURLIBNAME/Loggers/NoOpLogger.cs b/src/DotNetOAuth/Loggers/NoOpLogger.cs
index 768bd70..2f48b3c 100644
--- a/src/YOURLIBNAME/Loggers/NoOpLogger.cs
+++ b/src/DotNetOAuth/Loggers/NoOpLogger.cs
@@ -1,6 +1,6 @@
// <auto-generated />
-namespace YOURLIBNAME.Loggers {
+namespace DotNetOAuth.Loggers {
using System;
internal class NoOpLogger : ILog {
diff --git a/src/YOURLIBNAME/Loggers/TraceLogger.cs b/src/DotNetOAuth/Loggers/TraceLogger.cs
index 0e48188..c55d6df 100644
--- a/src/YOURLIBNAME/Loggers/TraceLogger.cs
+++ b/src/DotNetOAuth/Loggers/TraceLogger.cs
@@ -1,6 +1,6 @@
// <auto-generated />
-namespace YOURLIBNAME.Loggers {
+namespace DotNetOAuth.Loggers {
using System;
using System.Diagnostics;
using System.Security;
diff --git a/src/DotNetOAuth/Messaging/Channel.cs b/src/DotNetOAuth/Messaging/Channel.cs
new file mode 100644
index 0000000..40261b1
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/Channel.cs
@@ -0,0 +1,354 @@
+//-----------------------------------------------------------------------
+// <copyright file="Channel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Net;
+ using System.Text;
+ using System.Web;
+
+ /// <summary>
+ /// Manages sending direct messages to a remote party and receiving responses.
+ /// </summary>
+ internal abstract class Channel {
+ /// <summary>
+ /// The maximum allowable size for a 301 Redirect response before we send
+ /// a 200 OK response with a scripted form POST with the parameters instead
+ /// in order to ensure successfully sending a large payload to another server
+ /// that might have a maximum allowable size restriction on its GET request.
+ /// </summary>
+ private static int indirectMessageGetToPostThreshold = 2 * 1024; // 2KB, recommended by OpenID group
+
+ /// <summary>
+ /// The template for indirect messages that require form POST to forward through the user agent.
+ /// </summary>
+ /// <remarks>
+ /// We are intentionally using " instead of the html single quote ' below because
+ /// the HtmlEncode'd values that we inject will only escape the double quote, so
+ /// only the double-quote used around these values is safe.
+ /// </remarks>
+ private static string indirectMessageFormPostFormat = @"
+<html>
+<body onload=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; document.getElementById('openid_message').submit()"">
+<form id=""openid_message"" action=""{0}"" method=""post"" accept-charset=""UTF-8"" enctype=""application/x-www-form-urlencoded"" onSubmit=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; return true;"">
+{1}
+ <input id=""submit_button"" type=""submit"" value=""Continue"" />
+</form>
+</body>
+</html>
+";
+
+ /// <summary>
+ /// A tool that can figure out what kind of message is being received
+ /// so it can be deserialized.
+ /// </summary>
+ private IMessageTypeProvider messageTypeProvider;
+
+ /// <summary>
+ /// Gets or sets the HTTP response to send as a reply to the current incoming HTTP request.
+ /// </summary>
+ private Response queuedIndirectOrResponseMessage;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Channel"/> class.
+ /// </summary>
+ /// <param name="messageTypeProvider">
+ /// A class prepared to analyze incoming messages and indicate what concrete
+ /// message types can deserialize from it.
+ /// </param>
+ protected Channel(IMessageTypeProvider messageTypeProvider) {
+ if (messageTypeProvider == null) {
+ throw new ArgumentNullException("messageTypeProvider");
+ }
+
+ this.messageTypeProvider = messageTypeProvider;
+ }
+
+ /// <summary>
+ /// Gets a tool that can figure out what kind of message is being received
+ /// so it can be deserialized.
+ /// </summary>
+ protected IMessageTypeProvider MessageTypeProvider {
+ get { return this.messageTypeProvider; }
+ }
+
+ /// <summary>
+ /// Retrieves the stored response for sending and clears it from the channel.
+ /// </summary>
+ /// <returns>The response to send as the HTTP response.</returns>
+ internal Response DequeueIndirectOrResponseMessage() {
+ Response response = this.queuedIndirectOrResponseMessage;
+ this.queuedIndirectOrResponseMessage = null;
+ return response;
+ }
+
+ /// <summary>
+ /// Queues an indirect message (either a request or response)
+ /// or direct message response for transmission to a remote party.
+ /// </summary>
+ /// <param name="message">The one-way message to send</param>
+ internal void Send(IProtocolMessage message) {
+ this.Send(message, null);
+ }
+
+ /// <summary>
+ /// Queues an indirect message (either a request or response)
+ /// or direct message response for transmission to a remote party.
+ /// </summary>
+ /// <param name="message">The one-way message to send</param>
+ /// <param name="inResponseTo">
+ /// If <paramref name="message"/> is a response to an incoming message, this is the incoming message.
+ /// This is useful for error scenarios in deciding just how to send the response message.
+ /// May be null.
+ /// </param>
+ internal void Send(IProtocolMessage message, IProtocolMessage inResponseTo) {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+
+ var directedMessage = message as IDirectedProtocolMessage;
+ if (directedMessage == null) {
+ // This is a response to a direct message.
+ this.SendDirectMessageResponse(message);
+ } else {
+ if (directedMessage.Recipient != null) {
+ // This is an indirect message request or reply.
+ this.SendIndirectMessage(directedMessage);
+ } else {
+ ProtocolException exception = message as ProtocolException;
+ if (exception != null) {
+ if (inResponseTo is IDirectedProtocolMessage) {
+ this.ReportErrorAsDirectResponse(exception);
+ } else {
+ this.ReportErrorToUser(exception);
+ }
+ } else {
+ throw new InvalidOperationException(MessagingStrings.DirectedMessageMissingRecipient);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the protocol message embedded in the given HTTP request, if present.
+ /// </summary>
+ /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
+ /// <remarks>
+ /// Requires an HttpContext.Current context.
+ /// </remarks>
+ internal IProtocolMessage ReadFromRequest() {
+ return this.ReadFromRequest(new HttpRequestInfo(HttpContext.Current.Request));
+ }
+
+ /// <summary>
+ /// Gets the protocol message that may be embedded in the given HTTP request.
+ /// </summary>
+ /// <param name="request">The request to search for an embedded message.</param>
+ /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
+ protected internal virtual IProtocolMessage ReadFromRequest(HttpRequestInfo request) {
+ if (request == null) {
+ throw new ArgumentNullException("request");
+ }
+
+ // Search Form data first, and if nothing is there search the QueryString
+ var fields = request.Form.ToDictionary();
+ if (fields.Count == 0) {
+ fields = request.QueryString.ToDictionary();
+ }
+
+ return this.Receive(fields);
+ }
+
+ /// <summary>
+ /// Gets the protocol message that may be in the given HTTP response stream.
+ /// </summary>
+ /// <param name="responseStream">The response that is anticipated to contain an OAuth message.</param>
+ /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
+ protected internal abstract IProtocolMessage ReadFromResponse(Stream responseStream);
+
+ /// <summary>
+ /// Sends a direct message to a remote party and waits for the response.
+ /// </summary>
+ /// <param name="request">The message to send.</param>
+ /// <returns>The remote party's response.</returns>
+ protected internal abstract IProtocolMessage Request(IDirectedProtocolMessage request);
+
+ /// <summary>
+ /// Deserializes a dictionary of values into a message.
+ /// </summary>
+ /// <param name="fields">The dictionary of values that were read from an HTTP request or response.</param>
+ /// <returns>The deserialized message.</returns>
+ protected virtual IProtocolMessage Receive(Dictionary<string, string> fields) {
+ Type messageType = null;
+ if (fields != null) {
+ messageType = this.MessageTypeProvider.GetRequestMessageType(fields);
+ }
+
+ // If there was no data, or we couldn't recognize it as a message, abort.
+ if (messageType == null) {
+ return null;
+ }
+
+ // We have a message! Assemble it.
+ var serializer = MessageSerializer.Get(messageType);
+ IProtocolMessage message = serializer.Deserialize(fields);
+
+ return message;
+ }
+
+ /// <summary>
+ /// Takes a message and temporarily stores it for sending as the hosting site's
+ /// HTTP response to the current request.
+ /// </summary>
+ /// <param name="response">The message to store for sending.</param>
+ protected void QueueIndirectOrResponseMessage(Response response) {
+ if (response == null) {
+ throw new ArgumentNullException("response");
+ }
+ if (this.queuedIndirectOrResponseMessage != null) {
+ throw new InvalidOperationException(MessagingStrings.QueuedMessageResponseAlreadyExists);
+ }
+
+ this.queuedIndirectOrResponseMessage = response;
+ }
+
+ /// <summary>
+ /// Queues an indirect message for transmittal via the user agent.
+ /// </summary>
+ /// <param name="message">The message to send.</param>
+ protected virtual void SendIndirectMessage(IDirectedProtocolMessage message) {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+
+ var serializer = MessageSerializer.Get(message.GetType());
+ var fields = serializer.Serialize(message);
+ Response response;
+ if (CalculateSizeOfPayload(fields) > indirectMessageGetToPostThreshold) {
+ response = this.CreateFormPostResponse(message, fields);
+ } else {
+ response = this.Create301RedirectResponse(message, fields);
+ }
+
+ this.QueueIndirectOrResponseMessage(response);
+ }
+
+ /// <summary>
+ /// Encodes an HTTP response that will instruct the user agent to forward a message to
+ /// some remote third party using a 301 Redirect GET method.
+ /// </summary>
+ /// <param name="message">The message to forward.</param>
+ /// <param name="fields">The pre-serialized fields from the message.</param>
+ /// <returns>The encoded HTTP response.</returns>
+ protected virtual Response Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ WebHeaderCollection headers = new WebHeaderCollection();
+ UriBuilder builder = new UriBuilder(message.Recipient);
+ MessagingUtilities.AppendQueryArgs(builder, fields);
+ headers.Add(HttpResponseHeader.Location, builder.Uri.AbsoluteUri);
+ Logger.DebugFormat("Redirecting to {0}", builder.Uri.AbsoluteUri);
+ Response response = new Response {
+ Status = HttpStatusCode.Redirect,
+ Headers = headers,
+ Body = new byte[0],
+ OriginalMessage = message
+ };
+
+ return response;
+ }
+
+ /// <summary>
+ /// Encodes an HTTP response that will instruct the user agent to forward a message to
+ /// some remote third party using a form POST method.
+ /// </summary>
+ /// <param name="message">The message to forward.</param>
+ /// <param name="fields">The pre-serialized fields from the message.</param>
+ /// <returns>The encoded HTTP response.</returns>
+ protected virtual Response CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ WebHeaderCollection headers = new WebHeaderCollection();
+ MemoryStream body = new MemoryStream();
+ StreamWriter bodyWriter = new StreamWriter(body);
+ StringBuilder hiddenFields = new StringBuilder();
+ foreach (var field in fields) {
+ hiddenFields.AppendFormat(
+ "\t<input type=\"hidden\" name=\"{0}\" value=\"{1}\" />\r\n",
+ HttpUtility.HtmlEncode(field.Key),
+ HttpUtility.HtmlEncode(field.Value));
+ }
+ bodyWriter.WriteLine(
+ indirectMessageFormPostFormat,
+ HttpUtility.HtmlEncode(message.Recipient.AbsoluteUri),
+ hiddenFields);
+ bodyWriter.Flush();
+ Response response = new Response {
+ Status = HttpStatusCode.Redirect,
+ Headers = headers,
+ Body = body.ToArray(),
+ OriginalMessage = message
+ };
+
+ return response;
+ }
+
+ /// <summary>
+ /// Queues a message for sending in the response stream where the fields
+ /// are sent in the response stream in querystring style.
+ /// </summary>
+ /// <param name="response">The message to send as a response.</param>
+ /// <remarks>
+ /// This method implements spec V1.0 section 5.3.
+ /// </remarks>
+ protected abstract void SendDirectMessageResponse(IProtocolMessage response);
+
+ /// <summary>
+ /// Reports an error to the user via the user agent.
+ /// </summary>
+ /// <param name="exception">The error information.</param>
+ protected abstract void ReportErrorToUser(ProtocolException exception);
+
+ /// <summary>
+ /// Sends an error result directly to the calling remote party according to the
+ /// rules of the protocol.
+ /// </summary>
+ /// <param name="exception">The error information.</param>
+ protected abstract void ReportErrorAsDirectResponse(ProtocolException exception);
+
+ /// <summary>
+ /// Calculates a fairly accurate estimation on the size of a message that contains
+ /// a given set of fields.
+ /// </summary>
+ /// <param name="fields">The fields that would be included in a message.</param>
+ /// <returns>The size (in bytes) of the message payload.</returns>
+ private static int CalculateSizeOfPayload(IDictionary<string, string> fields) {
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ int size = 0;
+ foreach (var field in fields) {
+ size += field.Key.Length;
+ size += field.Value.Length;
+ size += 2; // & and =
+ }
+ return size;
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/DictionaryXmlReader.cs b/src/DotNetOAuth/Messaging/DictionaryXmlReader.cs
new file mode 100644
index 0000000..7a5dc21
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/DictionaryXmlReader.cs
@@ -0,0 +1,91 @@
+//-----------------------------------------------------------------------
+// <copyright file="DictionaryXmlReader.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Linq;
+ using System.Xml;
+ using System.Xml.Linq;
+
+ /// <summary>
+ /// An XmlReader-looking object that actually reads from a dictionary.
+ /// </summary>
+ internal class DictionaryXmlReader {
+ /// <summary>
+ /// Creates an XmlReader that reads data out of a dictionary instead of XML.
+ /// </summary>
+ /// <param name="rootElement">The name of the root XML element.</param>
+ /// <param name="fields">The dictionary to read data from.</param>
+ /// <returns>The XmlReader that will read the data out of the given dictionary.</returns>
+ internal static XmlReader Create(XName rootElement, IDictionary<string, string> fields) {
+ if (rootElement == null) {
+ throw new ArgumentNullException("rootElement");
+ }
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ return CreateRoundtripReader(rootElement, fields);
+ }
+
+ /// <summary>
+ /// Creates an <see cref="XmlReader"/> that will read values out of a dictionary.
+ /// </summary>
+ /// <param name="rootElement">The surrounding root XML element to generate.</param>
+ /// <param name="fields">The dictionary to list values from.</param>
+ /// <returns>The generated <see cref="XmlReader"/>.</returns>
+ private static XmlReader CreateRoundtripReader(XName rootElement, IDictionary<string, string> fields) {
+ if (rootElement == null) {
+ throw new ArgumentNullException("rootElement");
+ }
+
+ MemoryStream stream = new MemoryStream();
+ XmlWriter writer = XmlWriter.Create(stream);
+ SerializeDictionaryToXml(writer, rootElement, fields);
+ writer.Flush();
+ stream.Seek(0, SeekOrigin.Begin);
+
+ // For debugging purposes.
+ StreamReader sr = new StreamReader(stream);
+ Trace.WriteLine(sr.ReadToEnd());
+ stream.Seek(0, SeekOrigin.Begin);
+
+ return XmlReader.Create(stream);
+ }
+
+ /// <summary>
+ /// Writes out the values in a dictionary as XML.
+ /// </summary>
+ /// <param name="writer">The <see cref="XmlWriter"/> to write out the XML to.</param>
+ /// <param name="rootElement">The name of the root element to use to surround the dictionary values.</param>
+ /// <param name="fields">The dictionary with values to serialize.</param>
+ private static void SerializeDictionaryToXml(XmlWriter writer, XName rootElement, IDictionary<string, string> fields) {
+ if (writer == null) {
+ throw new ArgumentNullException("writer");
+ }
+ if (rootElement == null) {
+ throw new ArgumentNullException("rootElement");
+ }
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ writer.WriteStartElement(rootElement.LocalName, rootElement.NamespaceName);
+
+ // The elements must be serialized in alphabetical order so the DataContractSerializer will see them.
+ foreach (var pair in fields.OrderBy(pair => pair.Key, StringComparer.Ordinal)) {
+ writer.WriteStartElement(pair.Key, rootElement.NamespaceName);
+ writer.WriteValue(pair.Value);
+ writer.WriteEndElement();
+ }
+
+ writer.WriteEndElement();
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/DictionaryXmlWriter.cs b/src/DotNetOAuth/Messaging/DictionaryXmlWriter.cs
new file mode 100644
index 0000000..3be043f
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/DictionaryXmlWriter.cs
@@ -0,0 +1,273 @@
+//-----------------------------------------------------------------------
+// <copyright file="DictionaryXmlWriter.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using System.Xml;
+
+ /// <summary>
+ /// An XmlWriter-looking object that actually saves data to a dictionary.
+ /// </summary>
+ internal class DictionaryXmlWriter {
+ /// <summary>
+ /// Creates an <see cref="XmlWriter"/> that actually writes to an IDictionary&lt;string, string&gt; instance.
+ /// </summary>
+ /// <param name="dictionary">The dictionary to save the written XML to.</param>
+ /// <returns>The XmlWriter that will save data to the given dictionary.</returns>
+ internal static XmlWriter Create(IDictionary<string, string> dictionary) {
+ return new PseudoXmlWriter(dictionary);
+ }
+
+ /// <summary>
+ /// Writes out a dictionary as if it were XML.
+ /// </summary>
+ private class PseudoXmlWriter : XmlWriter {
+ /// <summary>
+ /// The dictionary to write values to.
+ /// </summary>
+ private IDictionary<string, string> dictionary;
+
+ /// <summary>
+ /// The key being written at the moment.
+ /// </summary>
+ private string key;
+
+ /// <summary>
+ /// The value being written out at the moment.
+ /// </summary>
+ private StringBuilder value = new StringBuilder();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PseudoXmlWriter"/> class.
+ /// </summary>
+ /// <param name="dictionary">The dictionary that will be written to.</param>
+ internal PseudoXmlWriter(IDictionary<string, string> dictionary) {
+ if (dictionary == null) {
+ throw new ArgumentNullException("dictionary");
+ }
+
+ this.dictionary = dictionary;
+ }
+
+ /// <summary>
+ /// Gets the spoofed state of the <see cref="XmlWriter"/>.
+ /// </summary>
+ public override WriteState WriteState {
+ get { return WriteState.Element; }
+ }
+
+ /// <summary>
+ /// Prepares to write out a new key/value pair with the given key name to the dictionary.
+ /// </summary>
+ /// <param name="prefix">This parameter is ignored.</param>
+ /// <param name="localName">The key to store in the dictionary.</param>
+ /// <param name="ns">This parameter is ignored.</param>
+ public override void WriteStartElement(string prefix, string localName, string ns) {
+ this.key = localName;
+ this.value.Length = 0;
+ }
+
+ /// <summary>
+ /// Appends some text to the value that is to be stored in the dictionary.
+ /// </summary>
+ /// <param name="text">The text to append to the value.</param>
+ public override void WriteString(string text) {
+ if (!string.IsNullOrEmpty(this.key)) {
+ this.value.Append(text);
+ }
+ }
+
+ /// <summary>
+ /// Writes out a completed key/value to the dictionary.
+ /// </summary>
+ public override void WriteEndElement() {
+ if (this.key != null) {
+ this.dictionary[this.key] = this.value.ToString();
+ this.key = null;
+ this.value.Length = 0;
+ }
+ }
+
+ /// <summary>
+ /// Clears the internal key/value building state.
+ /// </summary>
+ /// <param name="prefix">This parameter is ignored.</param>
+ /// <param name="localName">This parameter is ignored.</param>
+ /// <param name="ns">This parameter is ignored.</param>
+ public override void WriteStartAttribute(string prefix, string localName, string ns) {
+ this.key = null;
+ }
+
+ /// <summary>
+ /// This method does not do anything.
+ /// </summary>
+ public override void WriteEndAttribute() { }
+
+ /// <summary>
+ /// This method does not do anything.
+ /// </summary>
+ public override void Close() { }
+
+ #region Unimplemented methods
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ public override void Flush() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="ns">This parameter is ignored.</param>
+ /// <returns>None, since an exception is always thrown.</returns>
+ public override string LookupPrefix(string ns) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="buffer">This parameter is ignored.</param>
+ /// <param name="index">This parameter is ignored.</param>
+ /// <param name="count">This parameter is ignored.</param>
+ public override void WriteBase64(byte[] buffer, int index, int count) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="text">This parameter is ignored.</param>
+ public override void WriteCData(string text) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="ch">This parameter is ignored.</param>
+ public override void WriteCharEntity(char ch) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="buffer">This parameter is ignored.</param>
+ /// <param name="index">This parameter is ignored.</param>
+ /// <param name="count">This parameter is ignored.</param>
+ public override void WriteChars(char[] buffer, int index, int count) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="text">This parameter is ignored.</param>
+ public override void WriteComment(string text) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="name">This parameter is ignored.</param>
+ /// <param name="pubid">This parameter is ignored.</param>
+ /// <param name="sysid">This parameter is ignored.</param>
+ /// <param name="subset">This parameter is ignored.</param>
+ public override void WriteDocType(string name, string pubid, string sysid, string subset) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ public override void WriteEndDocument() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="name">This parameter is ignored.</param>
+ public override void WriteEntityRef(string name) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ public override void WriteFullEndElement() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="name">This parameter is ignored.</param>
+ /// <param name="text">This parameter is ignored.</param>
+ public override void WriteProcessingInstruction(string name, string text) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="data">This parameter is ignored.</param>
+ public override void WriteRaw(string data) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="buffer">This parameter is ignored.</param>
+ /// <param name="index">This parameter is ignored.</param>
+ /// <param name="count">This parameter is ignored.</param>
+ public override void WriteRaw(char[] buffer, int index, int count) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="standalone">This parameter is ignored.</param>
+ public override void WriteStartDocument(bool standalone) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ public override void WriteStartDocument() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="lowChar">This parameter is ignored.</param>
+ /// <param name="highChar">This parameter is ignored.</param>
+ public override void WriteSurrogateCharEntity(char lowChar, char highChar) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="ws">This parameter is ignored.</param>
+ public override void WriteWhitespace(string ws) {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/HttpRequestInfo.cs b/src/DotNetOAuth/Messaging/HttpRequestInfo.cs
new file mode 100644
index 0000000..3654367
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/HttpRequestInfo.cs
@@ -0,0 +1,137 @@
+//-----------------------------------------------------------------------
+// <copyright file="HttpRequestInfo.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Specialized;
+ using System.IO;
+ using System.Net;
+ using System.Web;
+
+ /// <summary>
+ /// A property store of details of an incoming HTTP request.
+ /// </summary>
+ /// <remarks>
+ /// This serves a very similar purpose to <see cref="HttpRequest"/>, except that
+ /// ASP.NET does not let us fully initialize that class, so we have to write one
+ /// of our one.
+ /// </remarks>
+ internal class HttpRequestInfo {
+ /// <summary>
+ /// The key/value pairs found in the entity of a POST request.
+ /// </summary>
+ private NameValueCollection form;
+
+ /// <summary>
+ /// The key/value pairs found in the querystring of the incoming request.
+ /// </summary>
+ private NameValueCollection queryString;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class.
+ /// </summary>
+ internal HttpRequestInfo() {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class.
+ /// </summary>
+ /// <param name="request">The ASP.NET structure to copy from.</param>
+ internal HttpRequestInfo(HttpRequest request) {
+ this.HttpMethod = request.HttpMethod;
+ this.Url = request.Url;
+ this.Headers = GetHeaderCollection(request.Headers);
+ this.InputStream = request.InputStream;
+
+ // These values would normally be calculated, but we'll reuse them from
+ // HttpRequest since they're already calculated, and there's a chance (<g>)
+ // that ASP.NET does a better job of being comprehensive about gathering
+ // these as well.
+ this.form = request.Form;
+ this.queryString = request.QueryString;
+ }
+
+ /// <summary>
+ /// Gets or sets the verb in the request (i.e. GET, POST, etc.)
+ /// </summary>
+ internal string HttpMethod { get; set; }
+
+ /// <summary>
+ /// Gets or sets the entire URL of the request.
+ /// </summary>
+ internal Uri Url { get; set; }
+
+ /// <summary>
+ /// Gets the query part of the URL (The ? and everything after it).
+ /// </summary>
+ internal string Query {
+ get { return this.Url != null ? this.Url.Query : null; }
+ }
+
+ /// <summary>
+ /// Gets or sets the collection of headers that came in with the request.
+ /// </summary>
+ internal WebHeaderCollection Headers { get; set; }
+
+ /// <summary>
+ /// Gets or sets the entity, or body of the request, if any.
+ /// </summary>
+ internal Stream InputStream { get; set; }
+
+ /// <summary>
+ /// Gets the key/value pairs found in the entity of a POST request.
+ /// </summary>
+ internal NameValueCollection Form {
+ get {
+ if (this.form == null) {
+ if (this.HttpMethod == "POST" && this.Headers[HttpRequestHeader.ContentType] == "application/x-www-form-urlencoded") {
+ StreamReader reader = new StreamReader(this.InputStream);
+ long originalPosition = this.InputStream.Position;
+ this.form = HttpUtility.ParseQueryString(reader.ReadToEnd());
+ if (this.InputStream.CanSeek) {
+ this.InputStream.Seek(originalPosition, SeekOrigin.Begin);
+ }
+ } else {
+ this.form = new NameValueCollection();
+ }
+ }
+
+ return this.form;
+ }
+ }
+
+ /// <summary>
+ /// Gets the key/value pairs found in the querystring of the incoming request.
+ /// </summary>
+ internal NameValueCollection QueryString {
+ get {
+ if (this.queryString == null) {
+ this.queryString = HttpUtility.ParseQueryString(this.Query);
+ }
+
+ return this.queryString;
+ }
+ }
+
+ /// <summary>
+ /// Converts a NameValueCollection to a WebHeaderCollection.
+ /// </summary>
+ /// <param name="pairs">The collection a HTTP headers.</param>
+ /// <returns>A new collection of the given headers.</returns>
+ private static WebHeaderCollection GetHeaderCollection(NameValueCollection pairs) {
+ if (pairs == null) {
+ throw new ArgumentNullException("pairs");
+ }
+
+ WebHeaderCollection headers = new WebHeaderCollection();
+ foreach (string key in pairs) {
+ headers.Add(key, pairs[key]);
+ }
+
+ return headers;
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/IDirectedProtocolMessage.cs b/src/DotNetOAuth/Messaging/IDirectedProtocolMessage.cs
new file mode 100644
index 0000000..bebd303
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/IDirectedProtocolMessage.cs
@@ -0,0 +1,20 @@
+//-----------------------------------------------------------------------
+// <copyright file="IDirectedProtocolMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+
+ /// <summary>
+ /// Implemented by messages that have explicit recipients
+ /// (direct requests and all indirect messages).
+ /// </summary>
+ internal interface IDirectedProtocolMessage : IProtocolMessage {
+ /// <summary>
+ /// Gets the URL of the intended receiver of this message.
+ /// </summary>
+ Uri Recipient { get; }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/IMessageTypeProvider.cs b/src/DotNetOAuth/Messaging/IMessageTypeProvider.cs
new file mode 100644
index 0000000..17affcb
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/IMessageTypeProvider.cs
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------
+// <copyright file="IMessageTypeProvider.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+
+ /// <summary>
+ /// A tool to analyze an incoming message to figure out what concrete class
+ /// is designed to deserialize it.
+ /// </summary>
+ internal interface IMessageTypeProvider {
+ /// <summary>
+ /// Analyzes an incoming request message payload to discover what kind of
+ /// message is embedded in it and returns the type, or null if no match is found.
+ /// </summary>
+ /// <param name="fields">The name/value pairs that make up the message payload.</param>
+ /// <returns>
+ /// The <see cref="IProtocolMessage"/>-derived concrete class that this message can
+ /// deserialize to. Null if the request isn't recognized as a valid protocol message.
+ /// </returns>
+ Type GetRequestMessageType(IDictionary<string, string> fields);
+
+ /// <summary>
+ /// Analyzes an incoming request message payload to discover what kind of
+ /// message is embedded in it and returns the type, or null if no match is found.
+ /// </summary>
+ /// <param name="request">
+ /// The message that was sent as a request that resulted in the response.
+ /// </param>
+ /// <param name="fields">The name/value pairs that make up the message payload.</param>
+ /// <returns>
+ /// The <see cref="IProtocolMessage"/>-derived concrete class that this message can
+ /// deserialize to. Null if the request isn't recognized as a valid protocol message.
+ /// </returns>
+ Type GetResponseMessageType(IProtocolMessage request, IDictionary<string, string> fields);
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/IProtocolMessage.cs b/src/DotNetOAuth/Messaging/IProtocolMessage.cs
new file mode 100644
index 0000000..3d9f82e
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/IProtocolMessage.cs
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------
+// <copyright file="IProtocolMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+
+ /// <summary>
+ /// The interface that classes must implement to be serialized/deserialized
+ /// as OAuth messages.
+ /// </summary>
+ internal interface IProtocolMessage {
+ /// <summary>
+ /// Gets the version of the protocol this message is prepared to implement.
+ /// </summary>
+ Protocol Protocol { get; }
+
+ /// <summary>
+ /// Gets whether this is a direct or indirect message.
+ /// </summary>
+ [Obsolete("Are we using this anywhere?")]
+ MessageTransport Transport { get; }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ void EnsureValidMessage();
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/MessageScheme.cs b/src/DotNetOAuth/Messaging/MessageScheme.cs
new file mode 100644
index 0000000..ee91740
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/MessageScheme.cs
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessageScheme.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ /// <summary>
+ /// The methods available for the Consumer to send direct messages to the Service Provider.
+ /// </summary>
+ /// <remarks>
+ /// See 1.0 spec section 5.2.
+ /// </remarks>
+ internal enum MessageScheme {
+ /// <summary>
+ /// In the HTTP Authorization header as defined in OAuth HTTP Authorization Scheme (OAuth HTTP Authorization Scheme).
+ /// </summary>
+ AuthorizationHeaderRequest,
+
+ /// <summary>
+ /// As the HTTP POST request body with a content-type of application/x-www-form-urlencoded.
+ /// </summary>
+ PostRequest,
+
+ /// <summary>
+ /// Added to the URLs in the query part (as defined by [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) section 3).
+ /// </summary>
+ GetRequest,
+
+ /// <summary>
+ /// Response parameters are sent by the Service Provider to return Tokens and other information to the Consumer in the HTTP response body. The parameter names and values are first encoded as per Parameter Encoding (Parameter Encoding), and concatenated with the ‘&amp;’ character (ASCII code 38) as defined in [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) Section 2.1.
+ /// </summary>
+ QueryStyleResponse,
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/MessageSerializer.cs b/src/DotNetOAuth/Messaging/MessageSerializer.cs
new file mode 100644
index 0000000..ac40232
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/MessageSerializer.cs
@@ -0,0 +1,167 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessageSerializer.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Linq;
+ using System.Runtime.Serialization;
+ using System.Xml;
+ using System.Xml.Linq;
+
+ /// <summary>
+ /// Serializes/deserializes OAuth messages for/from transit.
+ /// </summary>
+ internal class MessageSerializer {
+ /// <summary>
+ /// The serializer that will be used as a reflection engine to extract
+ /// the OAuth message properties out of their containing <see cref="IProtocolMessage"/>
+ /// objects.
+ /// </summary>
+ private readonly DataContractSerializer serializer;
+
+ /// <summary>
+ /// The specific <see cref="IProtocolMessage"/>-derived type
+ /// that will be serialized and deserialized using this class.
+ /// </summary>
+ private readonly Type messageType;
+
+ /// <summary>
+ /// An AppDomain-wide cache of shared serializers for optimization purposes.
+ /// </summary>
+ private static Dictionary<Type, MessageSerializer> prebuiltSerializers = new Dictionary<Type, MessageSerializer>();
+
+ /// <summary>
+ /// Backing field for the <see cref="RootElement"/> property
+ /// </summary>
+ private XName rootElement;
+
+ /// <summary>
+ /// Initializes a new instance of the MessageSerializer class.
+ /// </summary>
+ /// <param name="messageType">The specific <see cref="IProtocolMessage"/>-derived type
+ /// that will be serialized and deserialized using this class.</param>
+ private MessageSerializer(Type messageType) {
+ if (messageType == null) {
+ throw new ArgumentNullException("messageType");
+ }
+ if (!typeof(IProtocolMessage).IsAssignableFrom(messageType)) {
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ MessagingStrings.UnexpectedType,
+ typeof(IProtocolMessage).FullName,
+ messageType.FullName),
+ "messageType");
+ }
+
+ this.messageType = messageType;
+ this.serializer = new DataContractSerializer(
+ messageType, this.RootElement.LocalName, this.RootElement.NamespaceName);
+ }
+
+ /// <summary>
+ /// Gets the XML element that is used to surround all the XML values from the dictionary.
+ /// </summary>
+ private XName RootElement {
+ get {
+ if (this.rootElement == null) {
+ DataContractAttribute attribute = this.messageType.GetCustomAttributes(typeof(DataContractAttribute), false).OfType<DataContractAttribute>().Single();
+ this.rootElement = XName.Get("root", attribute.Namespace);
+ }
+
+ return this.rootElement;
+ }
+ }
+
+ /// <summary>
+ /// Returns a message serializer from a reusable collection of serializers.
+ /// </summary>
+ /// <param name="messageType">The type of message that will be serialized/deserialized.</param>
+ /// <returns>A previously created serializer if one exists, or a newly created one.</returns>
+ internal static MessageSerializer Get(Type messageType) {
+ if (messageType == null) {
+ throw new ArgumentNullException("messageType");
+ }
+
+ // We do this as efficiently as possible by first trying to fetch the
+ // serializer out of the dictionary without taking a lock.
+ MessageSerializer serializer;
+ if (prebuiltSerializers.TryGetValue(messageType, out serializer)) {
+ return serializer;
+ }
+
+ // Since it wasn't there, we'll be trying to write to the dictionary so
+ // we take a lock and try reading again first, then creating the serializer
+ // and storing it when we're sure it absolutely necessary.
+ lock (prebuiltSerializers) {
+ if (prebuiltSerializers.TryGetValue(messageType, out serializer)) {
+ return serializer;
+ }
+ serializer = new MessageSerializer(messageType);
+ prebuiltSerializers.Add(messageType, serializer);
+ }
+ return serializer;
+ }
+
+ /// <summary>
+ /// Reads the data from a message instance and returns a series of name=value pairs for the fields that must be included in the message.
+ /// </summary>
+ /// <param name="message">The message to be serialized.</param>
+ /// <returns>The dictionary of values to send for the message.</returns>
+ internal IDictionary<string, string> Serialize(IProtocolMessage message) {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+
+ var fields = new Dictionary<string, string>(StringComparer.Ordinal);
+ this.Serialize(fields, message);
+ return fields;
+ }
+
+ /// <summary>
+ /// Saves the [DataMember] properties of a message to an existing dictionary.
+ /// </summary>
+ /// <param name="fields">The dictionary to save values to.</param>
+ /// <param name="message">The message to pull values from.</param>
+ internal void Serialize(IDictionary<string, string> fields, IProtocolMessage message) {
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+
+ message.EnsureValidMessage();
+ using (XmlWriter writer = DictionaryXmlWriter.Create(fields)) {
+ this.serializer.WriteObjectContent(writer, message);
+ }
+ }
+
+ /// <summary>
+ /// Reads name=value pairs into an OAuth message.
+ /// </summary>
+ /// <param name="fields">The name=value pairs that were read in from the transport.</param>
+ /// <returns>The instantiated and initialized <see cref="IProtocolMessage"/> instance.</returns>
+ internal IProtocolMessage Deserialize(IDictionary<string, string> fields) {
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ var reader = DictionaryXmlReader.Create(this.RootElement, fields);
+ IProtocolMessage result;
+ try {
+ result = (IProtocolMessage)this.serializer.ReadObject(reader, false);
+ } catch (SerializationException ex) {
+ // Missing required fields is one cause of this exception.
+ throw new ProtocolException(Strings.InvalidIncomingMessage, ex);
+ }
+ result.EnsureValidMessage();
+ return result;
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/MessageTransport.cs b/src/DotNetOAuth/Messaging/MessageTransport.cs
new file mode 100644
index 0000000..40e6897
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/MessageTransport.cs
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessageTransport.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ /// <summary>
+ /// The type of transport mechanism used for a message: either direct or indirect.
+ /// </summary>
+ public enum MessageTransport {
+ /// <summary>
+ /// A message that is sent directly from the Consumer to the Service Provider, or vice versa.
+ /// </summary>
+ Direct,
+
+ /// <summary>
+ /// A message that is sent from one party to another via a redirect in the user agent.
+ /// </summary>
+ Indirect,
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs
new file mode 100644
index 0000000..98e8cc6
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs
@@ -0,0 +1,108 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:2.0.50727.3053
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class MessagingStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal MessagingStrings() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOAuth.Messaging.MessagingStrings", typeof(MessagingStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An instance of type {0} was expected, but received unexpected derived type {1}..
+ /// </summary>
+ internal static string DerivedTypeNotExpected {
+ get {
+ return ResourceManager.GetString("DerivedTypeNotExpected", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The directed message&apos;s Recipient property must not be null..
+ /// </summary>
+ internal static string DirectedMessageMissingRecipient {
+ get {
+ return ResourceManager.GetString("DirectedMessageMissingRecipient", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Error occurred while sending a direct message or gettings the response..
+ /// </summary>
+ internal static string ErrorInRequestReplyMessage {
+ get {
+ return ResourceManager.GetString("ErrorInRequestReplyMessage", 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 {
+ get {
+ return ResourceManager.GetString("QueuedMessageResponseAlreadyExists", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The type {0} or a derived type was expected, but {1} was given..
+ /// </summary>
+ internal static string UnexpectedType {
+ get {
+ return ResourceManager.GetString("UnexpectedType", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/MessagingStrings.resx b/src/DotNetOAuth/Messaging/MessagingStrings.resx
new file mode 100644
index 0000000..2125e15
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/MessagingStrings.resx
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="DerivedTypeNotExpected" xml:space="preserve">
+ <value>An instance of type {0} was expected, but received unexpected derived type {1}.</value>
+ </data>
+ <data name="DirectedMessageMissingRecipient" xml:space="preserve">
+ <value>The directed message's Recipient property must not be null.</value>
+ </data>
+ <data name="ErrorInRequestReplyMessage" xml:space="preserve">
+ <value>Error occurred while sending a direct message or gettings the response.</value>
+ </data>
+ <data name="QueuedMessageResponseAlreadyExists" xml:space="preserve">
+ <value>A message response is already queued for sending in the response stream.</value>
+ </data>
+ <data name="UnexpectedType" xml:space="preserve">
+ <value>The type {0} or a derived type was expected, but {1} was given.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOAuth/Messaging/MessagingUtilities.cs b/src/DotNetOAuth/Messaging/MessagingUtilities.cs
new file mode 100644
index 0000000..e8069f6
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/MessagingUtilities.cs
@@ -0,0 +1,119 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessagingUtilities.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.Net;
+ using System.Text;
+ using System.Web;
+
+ /// <summary>
+ /// A grab-bag of utility methods useful for the channel stack of the protocol.
+ /// </summary>
+ internal static class MessagingUtilities {
+ /// <summary>
+ /// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance,
+ /// taking care to set some headers to the appropriate properties of
+ /// <see cref="HttpResponse" />
+ /// </summary>
+ /// <param name="headers">The headers to add.</param>
+ /// <param name="response">The <see cref="HttpResponse"/> instance to set the appropriate values to.</param>
+ internal static void ApplyHeadersToResponse(WebHeaderCollection headers, HttpResponse response) {
+ if (headers == null) {
+ throw new ArgumentNullException("headers");
+ }
+ if (response == null) {
+ throw new ArgumentNullException("response");
+ }
+ foreach (string headerName in headers) {
+ switch (headerName) {
+ case "Content-Type":
+ response.ContentType = headers[HttpResponseHeader.ContentType];
+ break;
+
+ // Add more special cases here as necessary.
+ default:
+ response.AddHeader(headerName, headers[headerName]);
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Concatenates a list of name-value pairs as key=value&amp;key=value,
+ /// taking care to properly encode each key and value for URL
+ /// transmission. No ? is prefixed to the string.
+ /// </summary>
+ /// <param name="args">The dictionary of key/values to read from.</param>
+ /// <returns>The formulated querystring style string.</returns>
+ internal static string CreateQueryString(IDictionary<string, string> args) {
+ if (args == null) {
+ throw new ArgumentNullException("args");
+ }
+ if (args.Count == 0) {
+ return string.Empty;
+ }
+ StringBuilder sb = new StringBuilder(args.Count * 10);
+
+ foreach (var p in args) {
+ sb.Append(HttpUtility.UrlEncode(p.Key));
+ sb.Append('=');
+ sb.Append(HttpUtility.UrlEncode(p.Value));
+ sb.Append('&');
+ }
+ sb.Length--; // remove trailing &
+
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// Adds a set of name-value pairs to the end of a given URL
+ /// as part of the querystring piece. Prefixes a ? or &amp; before
+ /// first element as necessary.
+ /// </summary>
+ /// <param name="builder">The UriBuilder to add arguments to.</param>
+ /// <param name="args">
+ /// The arguments to add to the query.
+ /// If null, <paramref name="builder"/> is not changed.
+ /// </param>
+ internal static void AppendQueryArgs(UriBuilder builder, IDictionary<string, string> args) {
+ if (builder == null) {
+ throw new ArgumentNullException("builder");
+ }
+
+ if (args != null && args.Count > 0) {
+ StringBuilder sb = new StringBuilder(50 + (args.Count * 10));
+ if (!string.IsNullOrEmpty(builder.Query)) {
+ sb.Append(builder.Query.Substring(1));
+ sb.Append('&');
+ }
+ sb.Append(CreateQueryString(args));
+
+ builder.Query = sb.ToString();
+ }
+ }
+
+ /// <summary>
+ /// Converts a <see cref="NameValueCollection"/> to an IDictionary&lt;string, string&gt;.
+ /// </summary>
+ /// <param name="nvc">The NameValueCollection to convert. May be null.</param>
+ /// <returns>The generated dictionary, or null if <paramref name="nvc"/> is null.</returns>
+ internal static Dictionary<string, string> ToDictionary(this NameValueCollection nvc) {
+ if (nvc == null) {
+ return null;
+ }
+
+ var dictionary = new Dictionary<string, string>();
+ foreach (string key in nvc) {
+ dictionary.Add(key, nvc[key]);
+ }
+
+ return dictionary;
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/Response.cs b/src/DotNetOAuth/Messaging/Response.cs
new file mode 100644
index 0000000..68950ff
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/Response.cs
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------
+// <copyright file="Response.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Net;
+ using System.Web;
+
+ /// <summary>
+ /// A protocol message (request or response) that passes between Consumer and Service Provider
+ /// via the user agent using a redirect or form POST submission,
+ /// OR a direct message response.
+ /// </summary>
+ /// <remarks>
+ /// <para>An instance of this type describes the HTTP response that must be sent
+ /// in response to the current HTTP request.</para>
+ /// <para>It is important that this response make up the entire HTTP response.
+ /// A hosting ASPX page should not be allowed to render its normal HTML output
+ /// after this response is sent. The normal rendered output of an ASPX page
+ /// can be canceled by calling <see cref="HttpResponse.End"/> after this message
+ /// is sent on the response stream.</para>
+ /// </remarks>
+ public class Response {
+ /// <summary>
+ /// Gets the headers that must be included in the response to the user agent.
+ /// </summary>
+ /// <remarks>
+ /// The headers in this collection are not meant to be a comprehensive list
+ /// of exactly what should be sent, but are meant to augment whatever headers
+ /// are generally included in a typical response.
+ /// </remarks>
+ public WebHeaderCollection Headers { get; internal set; }
+
+ /// <summary>
+ /// Gets the body of the HTTP response.
+ /// </summary>
+ public byte[] Body { get; internal set; }
+
+ /// <summary>
+ /// Gets the HTTP status code to use in the HTTP response.
+ /// </summary>
+ public HttpStatusCode Status { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets a reference to the actual protocol message that
+ /// is being sent via the user agent.
+ /// </summary>
+ internal IProtocolMessage OriginalMessage { get; set; }
+
+ /// <summary>
+ /// Automatically sends the appropriate response to the user agent.
+ /// Requires a current HttpContext.
+ /// </summary>
+ public void Send() {
+ if (HttpContext.Current == null) {
+ throw new InvalidOperationException(Strings.CurrentHttpContextRequired);
+ }
+
+ HttpContext.Current.Response.Clear();
+ HttpContext.Current.Response.StatusCode = (int)this.Status;
+ MessagingUtilities.ApplyHeadersToResponse(this.Headers, HttpContext.Current.Response);
+ if (this.Body != null && this.Body.Length > 0) {
+ HttpContext.Current.Response.OutputStream.Write(this.Body, 0, this.Body.Length);
+ HttpContext.Current.Response.OutputStream.Flush();
+ }
+ HttpContext.Current.Response.OutputStream.Close();
+ HttpContext.Current.Response.End();
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuthChannel.cs b/src/DotNetOAuth/OAuthChannel.cs
new file mode 100644
index 0000000..6271954
--- /dev/null
+++ b/src/DotNetOAuth/OAuthChannel.cs
@@ -0,0 +1,246 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthChannel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Net;
+ using System.Text;
+ using System.Web;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// An OAuth-specific implementation of the <see cref="Channel"/> class.
+ /// </summary>
+ internal class OAuthChannel : Channel {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthChannel"/> class.
+ /// </summary>
+ internal OAuthChannel()
+ : base(new OAuthMessageTypeProvider()) {
+ }
+
+ /// <summary>
+ /// Searches an incoming HTTP request for data that could be used to assemble
+ /// a protocol request message.
+ /// </summary>
+ /// <param name="request">The HTTP request to search.</param>
+ /// <returns>A dictionary of data in the request. Should never be null, but may be empty.</returns>
+ protected internal override IProtocolMessage ReadFromRequest(HttpRequestInfo request) {
+ if (request == null) {
+ throw new ArgumentNullException("request");
+ }
+
+ // First search the Authorization header. Use it exclusively if it's present.
+ string authorization = request.Headers[HttpRequestHeader.Authorization];
+ if (authorization != null) {
+ string[] authorizationSections = authorization.Split(';'); // TODO: is this the right delimiter?
+ string oauthPrefix = Protocol.Default.AuthorizationHeaderScheme + " ";
+
+ // The Authorization header may have multiple uses, and OAuth may be just one of them.
+ // Go through each one looking for an OAuth one.
+ foreach (string auth in authorizationSections) {
+ string trimmedAuth = auth.Trim();
+ if (trimmedAuth.StartsWith(oauthPrefix, StringComparison.Ordinal)) {
+ // We found an Authorization: OAuth header.
+ // Parse it according to the rules in section 5.4.1 of the V1.0 spec.
+ var fields = new Dictionary<string, string>();
+ foreach (string stringPair in trimmedAuth.Substring(oauthPrefix.Length).Split(',')) {
+ string[] keyValueStringPair = stringPair.Trim().Split('=');
+ string key = Uri.UnescapeDataString(keyValueStringPair[0]);
+ string value = Uri.UnescapeDataString(keyValueStringPair[1].Trim('"'));
+ fields.Add(key, value);
+ }
+
+ return this.Receive(fields);
+ }
+ }
+ }
+
+ // We didn't find an OAuth authorization header. Revert to other payload methods.
+ return base.ReadFromRequest(request);
+ }
+
+ /// <summary>
+ /// Gets the protocol message that may be in the given HTTP response stream.
+ /// </summary>
+ /// <param name="responseStream">The response that is anticipated to contain an OAuth message.</param>
+ /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
+ protected internal override IProtocolMessage ReadFromResponse(Stream responseStream) {
+ if (responseStream == null) {
+ throw new ArgumentNullException("responseStream");
+ }
+
+ using (StreamReader reader = new StreamReader(responseStream)) {
+ string response = reader.ReadToEnd();
+ var fields = HttpUtility.ParseQueryString(response).ToDictionary();
+ return Receive(fields);
+ }
+ }
+
+ /// <summary>
+ /// Sends a direct message to a remote party and waits for the response.
+ /// </summary>
+ /// <param name="request">The message to send.</param>
+ /// <returns>The remote party's response.</returns>
+ protected internal override IProtocolMessage Request(IDirectedProtocolMessage request) {
+ if (request == null) {
+ throw new ArgumentNullException("request");
+ }
+
+ HttpWebRequest httpRequest;
+
+ MessageScheme transmissionMethod = MessageScheme.AuthorizationHeaderRequest;
+ switch (transmissionMethod) {
+ case MessageScheme.AuthorizationHeaderRequest:
+ httpRequest = this.InitializeRequestAsAuthHeader(request);
+ break;
+ case MessageScheme.PostRequest:
+ httpRequest = this.InitializeRequestAsPost(request);
+ break;
+ case MessageScheme.GetRequest:
+ httpRequest = this.InitializeRequestAsGet(request);
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+
+ // Submit the request and await the reply.
+ Dictionary<string, string> responseFields;
+ try {
+ using (HttpWebResponse response = (HttpWebResponse)httpRequest.GetResponse()) {
+ using (StreamReader reader = new StreamReader(response.GetResponseStream())) {
+ string queryString = reader.ReadToEnd();
+ responseFields = HttpUtility.ParseQueryString(queryString).ToDictionary();
+ }
+ }
+ } catch (WebException ex) {
+ throw new ProtocolException(MessagingStrings.ErrorInRequestReplyMessage, ex);
+ }
+
+ Type messageType = this.MessageTypeProvider.GetResponseMessageType(request, responseFields);
+ var responseSerialize = MessageSerializer.Get(messageType);
+ var responseMessage = responseSerialize.Deserialize(responseFields);
+
+ return responseMessage;
+ }
+
+ /// <summary>
+ /// Queues a message for sending in the response stream where the fields
+ /// are sent in the response stream in querystring style.
+ /// </summary>
+ /// <param name="response">The message to send as a response.</param>
+ /// <remarks>
+ /// This method implements spec V1.0 section 5.3.
+ /// </remarks>
+ protected override void SendDirectMessageResponse(IProtocolMessage response) {
+ MessageSerializer serializer = MessageSerializer.Get(response.GetType());
+ var fields = serializer.Serialize(response);
+ string responseBody = MessagingUtilities.CreateQueryString(fields);
+
+ Response encodedResponse = new Response {
+ Body = Encoding.UTF8.GetBytes(responseBody),
+ OriginalMessage = response,
+ Status = System.Net.HttpStatusCode.OK,
+ Headers = new System.Net.WebHeaderCollection(),
+ };
+ this.QueueIndirectOrResponseMessage(encodedResponse);
+ }
+
+ /// <summary>
+ /// Reports an error to the user via the user agent.
+ /// </summary>
+ /// <param name="exception">The error information.</param>
+ protected override void ReportErrorToUser(ProtocolException exception) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Sends an error result directly to the calling remote party according to the
+ /// rules of the protocol.
+ /// </summary>
+ /// <param name="exception">The error information.</param>
+ protected override void ReportErrorAsDirectResponse(ProtocolException exception) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Prepares to send a request to the Service Provider via the Authorization header.
+ /// </summary>
+ /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param>
+ /// <returns>The web request ready to send.</returns>
+ /// <remarks>
+ /// This method implements OAuth 1.0 section 5.2, item #1 (described in section 5.4).
+ /// </remarks>
+ private HttpWebRequest InitializeRequestAsAuthHeader(IDirectedProtocolMessage requestMessage) {
+ var serializer = MessageSerializer.Get(requestMessage.GetType());
+ var fields = serializer.Serialize(requestMessage);
+
+ HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient);
+
+ StringBuilder authorization = new StringBuilder();
+ authorization.Append(requestMessage.Protocol.AuthorizationHeaderScheme);
+ authorization.Append(" ");
+ foreach (var pair in fields) {
+ string key = Uri.EscapeDataString(pair.Key);
+ string value = Uri.EscapeDataString(pair.Value);
+ authorization.Append(key);
+ authorization.Append("=\"");
+ authorization.Append(value);
+ authorization.Append("\",");
+ }
+ authorization.Length--; // remove trailing comma
+
+ httpRequest.Headers.Add(HttpRequestHeader.Authorization, authorization.ToString());
+
+ return httpRequest;
+ }
+
+ /// <summary>
+ /// Prepares to send a request to the Service Provider as the payload of a POST request.
+ /// </summary>
+ /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param>
+ /// <returns>The web request ready to send.</returns>
+ /// <remarks>
+ /// This method implements OAuth 1.0 section 5.2, item #2.
+ /// </remarks>
+ private HttpWebRequest InitializeRequestAsPost(IDirectedProtocolMessage requestMessage) {
+ var serializer = MessageSerializer.Get(requestMessage.GetType());
+ var fields = serializer.Serialize(requestMessage);
+
+ HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient);
+ httpRequest.Method = "POST";
+ httpRequest.ContentType = "application/x-www-form-urlencoded";
+ string requestBody = MessagingUtilities.CreateQueryString(fields);
+ httpRequest.ContentLength = requestBody.Length;
+ using (StreamWriter writer = new StreamWriter(httpRequest.GetRequestStream())) {
+ writer.Write(requestBody);
+ }
+
+ return httpRequest;
+ }
+
+ /// <summary>
+ /// Prepares to send a request to the Service Provider as the query string in a GET request.
+ /// </summary>
+ /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param>
+ /// <returns>The web request ready to send.</returns>
+ /// <remarks>
+ /// This method implements OAuth 1.0 section 5.2, item #3.
+ /// </remarks>
+ private HttpWebRequest InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) {
+ var serializer = MessageSerializer.Get(requestMessage.GetType());
+ var fields = serializer.Serialize(requestMessage);
+
+ UriBuilder builder = new UriBuilder(requestMessage.Recipient);
+ MessagingUtilities.AppendQueryArgs(builder, fields);
+ HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri);
+
+ return httpRequest;
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuthMessageTypeProvider.cs b/src/DotNetOAuth/OAuthMessageTypeProvider.cs
new file mode 100644
index 0000000..7d4dbcd
--- /dev/null
+++ b/src/DotNetOAuth/OAuthMessageTypeProvider.cs
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthMessageTypeProvider.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// An OAuth-protocol specific implementation of the <see cref="IMessageTypeProvider"/>
+ /// interface.
+ /// </summary>
+ internal class OAuthMessageTypeProvider : IMessageTypeProvider {
+ #region IMessageTypeProvider Members
+
+ /// <summary>
+ /// Analyzes an incoming request message payload to discover what kind of
+ /// message is embedded in it and returns the type, or null if no match is found.
+ /// </summary>
+ /// <param name="fields">The name/value pairs that make up the message payload.</param>
+ /// <returns>
+ /// The <see cref="IProtocolMessage"/>-derived concrete class that this message can
+ /// deserialize to. Null if the request isn't recognized as a valid protocol message.
+ /// </returns>
+ public Type GetRequestMessageType(IDictionary<string, string> fields) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Analyzes an incoming request message payload to discover what kind of
+ /// message is embedded in it and returns the type, or null if no match is found.
+ /// </summary>
+ /// <param name="request">
+ /// The message that was sent as a request that resulted in the response.
+ /// </param>
+ /// <param name="fields">The name/value pairs that make up the message payload.</param>
+ /// <returns>
+ /// The <see cref="IProtocolMessage"/>-derived concrete class that this message can
+ /// deserialize to. Null if the request isn't recognized as a valid protocol message.
+ /// </returns>
+ public Type GetResponseMessageType(IProtocolMessage request, IDictionary<string, string> fields) {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/YOURLIBNAME/Properties/AssemblyInfo.cs b/src/DotNetOAuth/Properties/AssemblyInfo.cs
index 14abe90..725caf8 100644
--- a/src/YOURLIBNAME/Properties/AssemblyInfo.cs
+++ b/src/DotNetOAuth/Properties/AssemblyInfo.cs
@@ -28,16 +28,16 @@ using System.Security;
using System.Security.Permissions;
using System.Web.UI;
-[assembly: TagPrefix("YOURLIBNAME", "oauth")]
+[assembly: TagPrefix("DotNetOAuth", "oauth")]
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("YOURLIBNAME")]
+[assembly: AssemblyTitle("DotNetOAuth")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("YOURLIBNAME")]
+[assembly: AssemblyProduct("DotNetOAuth")]
[assembly: AssemblyCopyright("Copyright © 2008")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -57,9 +57,9 @@ using System.Web.UI;
// keep this assembly from being useful to shared host (medium trust) web sites.
[assembly: AllowPartiallyTrustedCallers]
-[assembly: InternalsVisibleTo("YOURLIBNAME.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DotNetOAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
#else
-[assembly: InternalsVisibleTo("YOURLIBNAME.Test")]
+[assembly: InternalsVisibleTo("DotNetOAuth.Test")]
#endif
// Specify what permissions are required and optional for the assembly.
diff --git a/src/DotNetOAuth/Protocol.cs b/src/DotNetOAuth/Protocol.cs
new file mode 100644
index 0000000..f3dc3b6
--- /dev/null
+++ b/src/DotNetOAuth/Protocol.cs
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------
+// <copyright file="Protocol.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// Constants used in the OAuth protocol.
+ /// </summary>
+ /// <remarks>
+ /// OAuth Protocol Parameter names and values are case sensitive. Each OAuth Protocol Parameters MUST NOT appear more than once per request, and are REQUIRED unless otherwise noted,
+ /// per OAuth 1.0 section 5.
+ /// </remarks>
+ internal class Protocol {
+ /// <summary>
+ /// The namespace to use for V1.0 of the protocol.
+ /// </summary>
+ internal const string DataContractNamespaceV10 = "http://oauth.net/core/1.0/";
+
+ /// <summary>
+ /// Gets the <see cref="Protocol"/> instance with values initialized for V1.0 of the protocol.
+ /// </summary>
+ internal static readonly Protocol V10 = new Protocol {
+ dataContractNamespace = DataContractNamespaceV10,
+ };
+
+ /// <summary>
+ /// The namespace to use for this version of the protocol.
+ /// </summary>
+ private string dataContractNamespace;
+
+ /// <summary>
+ /// The prefix used for all key names in the protocol.
+ /// </summary>
+ private string parameterPrefix = "oauth_";
+
+ /// <summary>
+ /// The scheme to use in Authorization header message requests.
+ /// </summary>
+ private string authorizationHeaderScheme = "OAuth";
+
+ /// <summary>
+ /// Gets the default <see cref="Protocol"/> instance.
+ /// </summary>
+ internal static Protocol Default { get { return V10; } }
+
+ /// <summary>
+ /// Gets the namespace to use for this version of the protocol.
+ /// </summary>
+ internal string DataContractNamespace {
+ get { return this.dataContractNamespace; }
+ }
+
+ /// <summary>
+ /// Gets the prefix used for all key names in the protocol.
+ /// </summary>
+ internal string ParameterPrefix {
+ get { return this.parameterPrefix; }
+ }
+
+ /// <summary>
+ /// Gets the scheme to use in Authorization header message requests.
+ /// </summary>
+ internal string AuthorizationHeaderScheme {
+ get { return this.authorizationHeaderScheme; }
+ }
+ }
+}
diff --git a/src/DotNetOAuth/ProtocolException.cs b/src/DotNetOAuth/ProtocolException.cs
new file mode 100644
index 0000000..4505c59
--- /dev/null
+++ b/src/DotNetOAuth/ProtocolException.cs
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------
+// <copyright file="ProtocolException.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth {
+ using System;
+
+ /// <summary>
+ /// An exception to represent errors in the local or remote implementation of the protocol.
+ /// </summary>
+ [Serializable]
+ public class ProtocolException : Exception {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ProtocolException"/> class.
+ /// </summary>
+ public ProtocolException() { }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ProtocolException"/> class.
+ /// </summary>
+ /// <param name="message">A message describing the specific error the occurred or was detected.</param>
+ public ProtocolException(string message) : base(message) { }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ProtocolException"/> class.
+ /// </summary>
+ /// <param name="message">A message describing the specific error the occurred or was detected.</param>
+ /// <param name="inner">The inner exception to include.</param>
+ public ProtocolException(string message, Exception inner) : base(message, inner) { }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ProtocolException"/> class.
+ /// </summary>
+ /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/>
+ /// that holds the serialized object data about the exception being thrown.</param>
+ /// <param name="context">The System.Runtime.Serialization.StreamingContext
+ /// that contains contextual information about the source or destination.</param>
+ protected ProtocolException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context)
+ : base(info, context) { }
+ }
+}
diff --git a/src/DotNetOAuth/ServiceProvider.cs b/src/DotNetOAuth/ServiceProvider.cs
new file mode 100644
index 0000000..49bfb9f
--- /dev/null
+++ b/src/DotNetOAuth/ServiceProvider.cs
@@ -0,0 +1,63 @@
+//-----------------------------------------------------------------------
+// <copyright file="ServiceProvider.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using System.Web;
+
+ /// <summary>
+ /// A web application that allows access via OAuth.
+ /// </summary>
+ /// <remarks>
+ /// <para>The Service Provider’s documentation should include:</para>
+ /// <list>
+ /// <item>The URLs (Request URLs) the Consumer will use when making OAuth requests, and the HTTP methods (i.e. GET, POST, etc.) used in the Request Token URL and Access Token URL.</item>
+ /// <item>Signature methods supported by the Service Provider.</item>
+ /// <item>Any additional request parameters that the Service Provider requires in order to obtain a Token. Service Provider specific parameters MUST NOT begin with oauth_.</item>
+ /// </list>
+ /// </remarks>
+ internal class ServiceProvider {
+ /// <summary>
+ /// The field used to store the value of the <see cref="RequestTokenUri"/> property.
+ /// </summary>
+ private Uri requestTokenUri;
+
+ /// <summary>
+ /// Gets or sets the URL used to obtain an unauthorized Request Token,
+ /// described in Section 6.1 (Obtaining an Unauthorized Request Token).
+ /// </summary>
+ /// <remarks>
+ /// The request URL query MUST NOT contain any OAuth Protocol Parameters.
+ /// </remarks>
+ /// <exception cref="ArgumentException">Thrown if this property is set to a URI with OAuth protocol parameters.</exception>
+ public Uri RequestTokenUri {
+ get {
+ return this.requestTokenUri;
+ }
+
+ set {
+ if (UriUtil.QueryStringContainsOAuthParameters(value)) {
+ throw new ArgumentException(Strings.RequestUrlMustNotHaveOAuthParameters);
+ }
+ this.requestTokenUri = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the URL used to obtain User authorization for Consumer access,
+ /// described in Section 6.2 (Obtaining User Authorization).
+ /// </summary>
+ public Uri UserAuthorizationUri { get; set; }
+
+ /// <summary>
+ /// Gets or sets the URL used to exchange the User-authorized Request Token
+ /// for an Access Token, described in Section 6.3 (Obtaining an Access Token).
+ /// </summary>
+ public Uri AccessTokenUri { get; set; }
+ }
+}
diff --git a/src/YOURLIBNAME/Settings.StyleCop b/src/DotNetOAuth/Settings.StyleCop
index 2eaae96..2eaae96 100644
--- a/src/YOURLIBNAME/Settings.StyleCop
+++ b/src/DotNetOAuth/Settings.StyleCop
diff --git a/src/DotNetOAuth/Strings.Designer.cs b/src/DotNetOAuth/Strings.Designer.cs
new file mode 100644
index 0000000..47a4c1c
--- /dev/null
+++ b/src/DotNetOAuth/Strings.Designer.cs
@@ -0,0 +1,90 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:2.0.50727.3053
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DotNetOAuth {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Strings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Strings() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOAuth.Strings", typeof(Strings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to HttpContext.Current is null. There must be an ASP.NET request in process for this operation to succeed..
+ /// </summary>
+ internal static string CurrentHttpContextRequired {
+ get {
+ return ResourceManager.GetString("CurrentHttpContextRequired", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An invalid OAuth message received and discarded..
+ /// </summary>
+ internal static string InvalidIncomingMessage {
+ get {
+ return ResourceManager.GetString("InvalidIncomingMessage", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The request URL query MUST NOT contain any OAuth Protocol Parameters..
+ /// </summary>
+ internal static string RequestUrlMustNotHaveOAuthParameters {
+ get {
+ return ResourceManager.GetString("RequestUrlMustNotHaveOAuthParameters", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Strings.resx b/src/DotNetOAuth/Strings.resx
new file mode 100644
index 0000000..0e718d8
--- /dev/null
+++ b/src/DotNetOAuth/Strings.resx
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="CurrentHttpContextRequired" xml:space="preserve">
+ <value>HttpContext.Current is null. There must be an ASP.NET request in process for this operation to succeed.</value>
+ </data>
+ <data name="InvalidIncomingMessage" xml:space="preserve">
+ <value>An invalid OAuth message received and discarded.</value>
+ </data>
+ <data name="RequestUrlMustNotHaveOAuthParameters" xml:space="preserve">
+ <value>The request URL query MUST NOT contain any OAuth Protocol Parameters.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOAuth/UriUtil.cs b/src/DotNetOAuth/UriUtil.cs
new file mode 100644
index 0000000..74f9e3e
--- /dev/null
+++ b/src/DotNetOAuth/UriUtil.cs
@@ -0,0 +1,31 @@
+//-----------------------------------------------------------------------
+// <copyright file="UriUtil.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth {
+ using System;
+ using System.Collections.Specialized;
+ using System.Linq;
+ using System.Web;
+
+ /// <summary>
+ /// Utility methods for working with URIs.
+ /// </summary>
+ internal class UriUtil {
+ /// <summary>
+ /// Tests a URI for the presence of an OAuth payload.
+ /// </summary>
+ /// <param name="uri">The URI to test.</param>
+ /// <returns>True if the URI contains an OAuth message.</returns>
+ internal static bool QueryStringContainsOAuthParameters(Uri uri) {
+ if (uri == null) {
+ return false;
+ }
+
+ NameValueCollection nvc = HttpUtility.ParseQueryString(uri.Query);
+ return nvc.Keys.OfType<string>().Any(key => key.StartsWith(Protocol.V10.ParameterPrefix, StringComparison.Ordinal));
+ }
+ }
+}
diff --git a/src/YOURLIBNAME/Util.cs b/src/DotNetOAuth/Util.cs
index 16f52fa..e3af5f1 100644
--- a/src/YOURLIBNAME/Util.cs
+++ b/src/DotNetOAuth/Util.cs
@@ -3,7 +3,7 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
-namespace YOURLIBNAME {
+namespace DotNetOAuth {
using System.Globalization;
using System.Reflection;
diff --git a/src/LocalTestRun.testrunconfig b/src/LocalTestRun.testrunconfig
index d03f62c..bad921b 100644
--- a/src/LocalTestRun.testrunconfig
+++ b/src/LocalTestRun.testrunconfig
@@ -3,7 +3,7 @@
<Description>This is a default test run configuration for a local test run.</Description>
<CodeCoverage enabled="true">
<Regular>
- <CodeCoverageItem binaryFile="C:\git\productname\bin\Debug\YOURLIBNAME.dll" pdbFile="C:\git\productname\bin\Debug\ProductName.pdb" instrumentInPlace="true" />
+ <CodeCoverageItem binaryFile="C:\git\dotnetoauth\bin\Debug\DotNetOAuth.dll" pdbFile="C:\git\dotnetoauth\bin\Debug\DotNetOAuth.pdb" instrumentInPlace="true" />
</Regular>
</CodeCoverage>
<TestTypeSpecific>
diff --git a/src/Settings.StyleCop b/src/Settings.StyleCop
index 8831d6b..a00cce5 100644
--- a/src/Settings.StyleCop
+++ b/src/Settings.StyleCop
@@ -17,6 +17,31 @@
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
+ <Rule Name="ElementMustNotBeOnSingleLine">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="ClosingCurlyBracketMustBeFollowedByBlankLine">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ </Rules>
+ <AnalyzerSettings />
+ </Analyzer>
+ <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.ReadabilityRules">
+ <Rules>
+ <Rule Name="ParametersMustBeOnSameLineOrSeparateLines">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
+ <Rule Name="CodeMustNotContainMultipleStatementsOnOneLine">
+ <RuleSettings>
+ <BooleanProperty Name="Enabled">False</BooleanProperty>
+ </RuleSettings>
+ </Rule>
</Rules>
<AnalyzerSettings />
</Analyzer>
diff --git a/src/YOURLIBNAME.Test/Settings.StyleCop b/src/YOURLIBNAME.Test/Settings.StyleCop
deleted file mode 100644
index 7f55ce6..0000000
--- a/src/YOURLIBNAME.Test/Settings.StyleCop
+++ /dev/null
@@ -1 +0,0 @@
-<StyleCopSettings Version="4.3" /> \ No newline at end of file
diff --git a/tools/Documentation.targets b/tools/Documentation.targets
index 9891de7..43b70d4 100644
--- a/tools/Documentation.targets
+++ b/tools/Documentation.targets
@@ -3,7 +3,7 @@
<NetfxVer>2.0</NetfxVer>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<ProjectRoot Condition="'$(ProjectRoot)' == ''">$(MSBuildProjectDirectory)\..\..</ProjectRoot>
- <OutputAssembly>YOURLIBNAME</OutputAssembly>
+ <OutputAssembly>DotNetOAuth</OutputAssembly>
<OutputPath>$(ProjectRoot)\bin\$(Configuration)</OutputPath>
<DocOutputPath>$(ProjectRoot)\doc</DocOutputPath>
<IntermediatePath>$(ProjectRoot)\obj\$(Configuration)</IntermediatePath>
diff --git a/tools/YOURLIBNAME.Common.Settings.targets b/tools/DotNetOAuth.Common.Settings.targets
index 040b7eb..1fc35a7 100644
--- a/tools/YOURLIBNAME.Common.Settings.targets
+++ b/tools/DotNetOAuth.Common.Settings.targets
@@ -1,12 +1,11 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
- <ProductName>YOURLIBNAME</ProductName>
+ <ProductName>DotNetOAuth</ProductName>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<ProjectRoot Condition="'$(ProjectRoot)' == ''">$(MSBuildProjectDirectory)</ProjectRoot>
<OutputPath>$(ProjectRoot)\bin\$(Configuration)</OutputPath>
<DocOutputPath>$(ProjectRoot)\doc</DocOutputPath>
<IntermediatePath>$(ProjectRoot)\obj\$(Configuration)</IntermediatePath>
<ToolsDir>$(ProjectRoot)\tools</ToolsDir>
-
-</PropertyGroup>
+ </PropertyGroup>
</Project> \ No newline at end of file
diff --git a/tools/YOURLIBNAME.Versioning.targets b/tools/DotNetOAuth.Versioning.targets
index 37c5a81..35d5f71 100644
--- a/tools/YOURLIBNAME.Versioning.targets
+++ b/tools/DotNetOAuth.Versioning.targets
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<!-- Import this .targets file to automaticaly generate AssemblyVersion
- attribute according to YOURLIBNAME convention. -->
+ attribute according to DotNetOAuth convention. -->
<PropertyGroup>
<ProjectRoot Condition="'$(ProjectRoot)' == ''">$(MSBuildProjectDirectory)\..\..</ProjectRoot>
<VersionCsFile>$(ProjectRoot)\obj\$(Configuration)\$(AssemblyName).Version.cs</VersionCsFile>
@@ -33,4 +33,4 @@
<Compile Include="$(VersionCsFile)" />
</ItemGroup>
</Target>
-</Project> \ No newline at end of file
+</Project>
diff --git a/tools/Sandcastle/Presentation/vs2005/Content/feedBack_content.xml b/tools/Sandcastle/Presentation/vs2005/Content/feedBack_content.xml
index 6fe375a..c526b34 100644
--- a/tools/Sandcastle/Presentation/vs2005/Content/feedBack_content.xml
+++ b/tools/Sandcastle/Presentation/vs2005/Content/feedBack_content.xml
@@ -1,6 +1,6 @@
<content xml:space="preserve">
- <item id="fb_alias">YOURLIBNAME@googlegroups.com</item>
+ <item id="fb_alias">DotNetOAuth@googlegroups.com</item>
<item id="fb_product"></item>
<item id="fb_deliverable"></item>
@@ -27,4 +27,4 @@
<item id="fb_Title">Documentation Feedback</item>
<item id="fb_altIcon">Display feedback instructions at the bottom of the page.</item>
-</content> \ No newline at end of file
+</content>
diff --git a/tools/Sandcastle/Presentation/vs2005/Content/reference_content.xml b/tools/Sandcastle/Presentation/vs2005/Content/reference_content.xml
index aa5d9a1..25e7b51 100644
--- a/tools/Sandcastle/Presentation/vs2005/Content/reference_content.xml
+++ b/tools/Sandcastle/Presentation/vs2005/Content/reference_content.xml
@@ -238,7 +238,7 @@
</item>
<!-- inserted boilerplate -->
- <item id="runningHeaderText">YOURLIBNAME Class Library</item>
+ <item id="runningHeaderText">DotNetOAuth Class Library</item>
<item id="rootLink"><referenceLink target="R:Project">Namespaces</referenceLink></item>
<item id="obsoleteShort"><span class="obsolete">Obsolete.</span></item>
<item id="obsoleteLong"><span class="obsolete">This API is obsolete.</span></item>
diff --git a/tools/Sandcastle/Presentation/vs2005/Content/shared_content.xml b/tools/Sandcastle/Presentation/vs2005/Content/shared_content.xml
index bf99b1b..02bd6ee 100644
--- a/tools/Sandcastle/Presentation/vs2005/Content/shared_content.xml
+++ b/tools/Sandcastle/Presentation/vs2005/Content/shared_content.xml
@@ -181,7 +181,7 @@
</includeAttribute>
feedback
</a>
- on this topic to the YOURLIBNAME group.
+ on this topic to the DotNetOAuth group.
</span>
</item>
diff --git a/tools/libcheck.ps1 b/tools/libcheck.ps1
index 25e49e3..d37c9e5 100644
--- a/tools/libcheck.ps1
+++ b/tools/libcheck.ps1
@@ -26,12 +26,12 @@ function Checkout($Version) {
}
function Build() {
- msbuild.exe "$RootDir\src\YOURLIBNAME\YOURLIBNAME.csproj" /p:Configuration=$Configuration
+ msbuild.exe "$RootDir\src\DotNetOAuth\DotNetOAuth.csproj" /p:Configuration=$Configuration
}
function Generate-Metadata($Version) {
Push-Location $LibCheckTmpDir
- & ".\libcheck.exe" -store "YOURLIBNAME.dll" $Version -full "$BinDir\$Configuration"
+ & ".\libcheck.exe" -store "DotNetOAuth.dll" $Version -full "$BinDir\$Configuration"
Pop-Location
}
@@ -43,7 +43,7 @@ function Compare-Metadata() {
function ShadowCopy-Libcheck() {
# This function copies LibCheck from the checked out version to a temp
- # directory so that as we git checkout other versions of YOURLIBNAME,
+ # directory so that as we git checkout other versions of DotNetOAuth,
# we can be sure of running one consistent version of LibCheck.
Remove-Item -Recurse $LibCheckTmpDir
Copy-Item -Recurse "$ToolsDir\LibCheck" (Split-Path $LibCheckTmpDir)
diff --git a/tools/sandcastle.targets b/tools/sandcastle.targets
index e717209..71272c0 100644
--- a/tools/sandcastle.targets
+++ b/tools/sandcastle.targets
@@ -108,12 +108,12 @@
<Target Name="Html"
Inputs="$(ManifestFile);$(ReflectionFile);$(DocumentationFile)"
- Outputs="$(DocOutputApiPath)\html\N_ProductName.htm"
+ Outputs="$(DocOutputApiPath)\html\N_DotNetOAuth.htm"
DependsOnTargets="SetEnvironmentVars;Template;Manifest">
<Exec Command='"$(ProductionTools)\BuildAssembler.exe" /config:"$(Presentation)\configuration\sandcastle.config" "$(ManifestFile)"' />
</Target>
- <Target Name="Chm" Inputs="$(DocOutputApiPath)\html\N_ProductName.htm;$(ReflectionFile)" Outputs="$(ChmFile)" DependsOnTargets="Html">
+ <Target Name="Chm" Inputs="$(DocOutputApiPath)\html\N_DotNetOAuth.htm;$(ReflectionFile)" Outputs="$(ChmFile)" DependsOnTargets="Html">
<MakeDir Directories="$(chmDir)" Condition="!Exists('$(chmDir)')" />
<MakeDir Directories="$(chmDir)\Html" Condition="!Exists('$(chmDir)\Html')" />
<MakeDir Directories="$(chmDir)\Icons" Condition="!Exists('$(chmDir)\Icons')" />