summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2010-04-16 16:38:06 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2010-04-16 16:38:06 -0700
commita23fa871be7ba3148c3182d2cfc5f89fbde0e895 (patch)
treee57e94f071189eb9a146047130ab6c9b230f739b
parenta195843e69344d977013421d0ae5de78750b018c (diff)
parent34080bfd3948a0dea0dac6d876a200a9a8017669 (diff)
downloadDotNetOpenAuth-a23fa871be7ba3148c3182d2cfc5f89fbde0e895.zip
DotNetOpenAuth-a23fa871be7ba3148c3182d2cfc5f89fbde0e895.tar.gz
DotNetOpenAuth-a23fa871be7ba3148c3182d2cfc5f89fbde0e895.tar.bz2
Merged in v3.4.3.
-rw-r--r--doc/specs/openid-ui-extension.html (renamed from doc/specs/openid_ui_extension_draft01.html)281
-rw-r--r--lib/Microsoft.Contracts.dllbin16384 -> 16896 bytes
-rw-r--r--projecttemplates/MvcRelyingParty.vsixmanifest8
-rw-r--r--projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj4
-rw-r--r--projecttemplates/WebFormsRelyingParty.vsixmanifest6
-rw-r--r--projecttemplates/projecttemplates.proj2
-rw-r--r--samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj1
-rw-r--r--samples/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs23
-rw-r--r--samples/DotNetOpenAuth.ApplicationBlock/YubikeyRelyingParty.cs207
-rw-r--r--samples/OpenIdOfflineProvider/MainWindow.xaml11
-rw-r--r--samples/OpenIdOfflineProvider/MainWindow.xaml.cs20
-rw-r--r--samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj12
-rw-r--r--samples/OpenIdProviderMvc/Web.config4
-rw-r--r--samples/OpenIdProviderWebForms/OpenIdProviderWebForms.csproj4
-rw-r--r--samples/OpenIdProviderWebForms/Web.config5
-rw-r--r--samples/OpenIdProviderWebForms/decide.aspx.cs7
-rw-r--r--samples/OpenIdProviderWebForms/login.aspx10
-rw-r--r--samples/OpenIdProviderWebForms/login.aspx.cs28
-rw-r--r--samples/OpenIdProviderWebForms/login.aspx.designer.cs39
-rw-r--r--samples/OpenIdRelyingPartyMvc/OpenIdRelyingPartyMvc.csproj12
-rw-r--r--samples/OpenIdRelyingPartyMvc/Web.config4
-rw-r--r--src/DotNetOpenAuth.Test/Logging.config3
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs2
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs21
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs128
-rw-r--r--src/DotNetOpenAuth/ComponentModel/ConverterBase.cs1
-rw-r--r--src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs2
-rw-r--r--src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd19
-rw-r--r--src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs20
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj4
-rw-r--r--src/DotNetOpenAuth/GlobalSuppressions.cs3
-rw-r--r--src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs65
-rw-r--r--src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs8
-rw-r--r--src/DotNetOpenAuth/InfoCard/Token/Token.cs22
-rw-r--r--src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs4
-rw-r--r--src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs13
-rw-r--r--src/DotNetOpenAuth/Messaging/Channel.cs41
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs11
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingStrings.resx3
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingUtilities.cs5
-rw-r--r--src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs6
-rw-r--r--src/DotNetOpenAuth/Migrated rules for DotNetOpenAuth.ruleset1
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs2
-rw-r--r--src/DotNetOpenAuth/OpenId/Association.cs28
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs6
-rw-r--r--src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs113
-rw-r--r--src/DotNetOpenAuth/OpenId/Identifier.cs62
-rw-r--r--src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs2
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs20
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.resx6
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs21
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs163
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs16
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs3
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs2
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs16
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs4
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs8
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs11
-rw-r--r--src/DotNetOpenAuth/OpenId/UriIdentifier.cs347
-rw-r--r--src/DotNetOpenAuth/Reporting.cs71
-rw-r--r--src/DotNetOpenAuth/Util.cs2
-rw-r--r--src/DotNetOpenAuth/XrdsPublisher.cs2
-rw-r--r--src/DotNetOpenAuth/Yadis/Yadis.cs2
-rw-r--r--tools/DotNetOpenAuth.automated.props1
-rw-r--r--tools/DotNetOpenAuth.targets1
-rw-r--r--tools/drop.proj7
-rw-r--r--vsix/extension.vsixmanifest6
68 files changed, 1590 insertions, 402 deletions
diff --git a/doc/specs/openid_ui_extension_draft01.html b/doc/specs/openid-ui-extension.html
index 71467f7..3327853 100644
--- a/doc/specs/openid_ui_extension_draft01.html
+++ b/doc/specs/openid-ui-extension.html
@@ -1,11 +1,11 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
-<html lang="en"><head>
-<title>Implementers' Draft: OpenID User Interface Extension 1.0 - DRAFT 0.4</title>
-<meta http-equiv="Expires" content="Tue, 19 May 2009 02:37:11 +0000">
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<meta name="description" content="OpenID User Interface Extension 1.0 - DRAFT 0.4">
+<!-- test -->
+<html lang="en"><head><title>Implementers' Draft: OpenID User Interface Extension 1.0 - DRAFT 0.5</title>
+<meta http-equiv="Expires" content="Tue, 23 Jun 2009 20:52:02 +0000">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<meta name="description" content="OpenID User Interface Extension 1.0 - DRAFT 0.5">
<meta name="generator" content="xml2rfc v1.33 (http://xml.resource.org/)">
-<style type="text/css"><!--
+<style type='text/css'><!--
body {
font-family: verdana, charcoal, helvetica, arial, sans-serif;
font-size: small; color: #000; background-color: #FFF;
@@ -139,16 +139,17 @@
color: #CCC; background-color: #CCC;
}
--></style>
-</head><body>
-<table summary="layout" class="TOCbug" align="right" cellpadding="0" cellspacing="2"><tbody><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></tbody></table>
-<table summary="layout" border="0" cellpadding="0" cellspacing="0" width="66%"><tbody><tr><td><table summary="layout" border="0" cellpadding="2" cellspacing="1" width="100%">
-<tbody><tr><td class="header">Implementers' Draft</td><td class="header">A. Tom</td></tr>
+</head>
+<body>
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
+<table summary="layout" width="66%" border="0" cellpadding="0" cellspacing="0"><tr><td><table summary="layout" width="100%" border="0" cellpadding="2" cellspacing="1">
+<tr><td class="header">Implementers' Draft</td><td class="header">A. Tom</td></tr>
<tr><td class="header">&nbsp;</td><td class="header">Yahoo!</td></tr>
<tr><td class="header">&nbsp;</td><td class="header">B. de Medeiros</td></tr>
<tr><td class="header">&nbsp;</td><td class="header">Google</td></tr>
<tr><td class="header">&nbsp;</td><td class="header">May 18, 2009</td></tr>
-</tbody></table></td></tr></tbody></table>
-<h1><br>OpenID User Interface Extension 1.0 - DRAFT 0.4</h1>
+</table></td></tr></table>
+<h1><br />OpenID User Interface Extension 1.0 - DRAFT 0.5</h1>
<h3>Abstract</h3>
@@ -156,53 +157,57 @@
This specification defines a mechanism to support OpenID user interfaces
optimized for different environments and languages.
-</p><a name="toc"></a><br><hr>
+</p><a name="toc"></a><br /><hr />
<h3>Table of Contents</h3>
<p class="toc">
<a href="#conv">1.</a>&nbsp;
-Notation and Conventions<br>
+Notation and Conventions<br />
<a href="#anchor1">2.</a>&nbsp;
-Overview<br>
+Overview<br />
<a href="#anchor2">3.</a>&nbsp;
-Extension Namespace<br>
+Extension Namespace<br />
<a href="#anchor3">4.</a>&nbsp;
-Language Preference<br>
+Language Preference<br />
<a href="#anchor4">5.</a>&nbsp;
-Requesting Authentication in a Popup<br>
+Requesting Authentication in a Popup<br />
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#anchor5">5.1.</a>&nbsp;
-Authentication Response in a Fragment<br>
+Authentication Response in a Fragment<br />
<a href="#anchor6">6.</a>&nbsp;
-Discovery<br>
+Requesting Display of RP icons in the OP Approval UI<br />
<a href="#anchor7">7.</a>&nbsp;
-Considerations<br>
+Discovery<br />
<a href="#anchor8">8.</a>&nbsp;
-Acknowledgements<br>
-<a href="#rfc.references1">9.</a>&nbsp;
-References<br>
-<a href="#rfc.authors">§</a>&nbsp;
-Authors' Addresses<br>
+Considerations<br />
+<a href="#anchor9">9.</a>&nbsp;
+Acknowledgements<br />
+<a href="#rfc.references1">10.</a>&nbsp;
+References<br />
+<a href="#anchor11">Appendix&nbsp;A.</a>&nbsp;
+Example Use of Experimental Mode<br />
+<a href="#rfc.authors">&#167;</a>&nbsp;
+Authors' Addresses<br />
</p>
-<br clear="all">
+<br clear="all" />
-<a name="conv"></a><br><hr>
-<table summary="layout" class="TOCbug" align="right" cellpadding="0" cellspacing="2"><tbody><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></tbody></table>
+<a name="conv"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
<a name="rfc.section.1"></a><h3>1.&nbsp;
Notation and Conventions</h3>
<p>The keywords 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>.
+ described in <a class='info' href='#RFC2119'>[RFC2119]<span> (</span><span class='info'>Bradner, B., &ldquo;Key words for use in RFCs to Indicate Requirement Levels,&rdquo; .</span><span>)</span></a>.
</p>
<p>
Unless otherwise noted, this specification is written as a direct
- continuation of <a class="info" href="#OpenID%202.0">[OpenID 2.0]<span> (</span><span class="info">OpenID 2.0 Workgroup, “OpenID 2.0,” .</span><span>)</span></a>, inheriting the definitions and
+ continuation of <a class='info' href='#OpenID 2.0'>[OpenID 2.0]<span> (</span><span class='info'>OpenID 2.0 Workgroup, &ldquo;OpenID 2.0,&rdquo; .</span><span>)</span></a>, inheriting the definitions and
guidelines set by it.
</p>
-<a name="anchor1"></a><br><hr>
-<table summary="layout" class="TOCbug" align="right" cellpadding="0" cellspacing="2"><tbody><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></tbody></table>
+<a name="anchor1"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
<a name="rfc.section.2"></a><h3>2.&nbsp;
Overview</h3>
@@ -231,17 +236,18 @@ Overview</h3>
to the user.
</p>
-<a name="anchor2"></a><br><hr>
-<table summary="layout" class="TOCbug" align="right" cellpadding="0" cellspacing="2"><tbody><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></tbody></table>
+<a name="anchor2"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
<a name="rfc.section.3"></a><h3>3.&nbsp;
Extension Namespace</h3>
<p>
All OpenID 2.0 messages that contain a UI Extension element MUST
contain the following extension namespace declaration, as specified in the
- Extensions section of <a class="info" href="#OpenID%202.0">[OpenID 2.0]<span> (</span><span class="info">OpenID 2.0 Workgroup, “OpenID 2.0,” .</span><span>)</span></a>.
+ Extensions section of <a class='info' href='#OpenID 2.0'>[OpenID 2.0]<span> (</span><span class='info'>OpenID 2.0 Workgroup, &ldquo;OpenID 2.0,&rdquo; .</span><span>)</span></a>.
-</p><div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre> openid.ns.&lt;alias&gt;=http://specs.openid.net/extensions/ui/1.0
+</p><div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre>
+ openid.ns.&lt;alias&gt;=http://specs.openid.net/extensions/ui/1.0
</pre></div>
<p>
The actual extension namespace alias should be determined on a per-message basis
@@ -250,8 +256,8 @@ Extension Namespace</h3>
for all examples is "ui".
</p>
-<a name="anchor3"></a><br><hr>
-<table summary="layout" class="TOCbug" align="right" cellpadding="0" cellspacing="2"><tbody><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></tbody></table>
+<a name="anchor3"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
<a name="rfc.section.4"></a><h3>4.&nbsp;
Language Preference</h3>
@@ -270,7 +276,7 @@ Language Preference</h3>
</dd>
<dt>openid.ui.lang</dt>
<dd>
- REQUIRED. The user's preferred languages as a <a class="info" href="#BCP%2047">[BCP 47]<span> (</span><span class="info">Phillips, A. and M. Davis, “Tags for Identifying Languages,” .</span><span>)</span></a> language priority list,
+ REQUIRED. The user's preferred languages as a <a class='info' href='#BCP 47'>[BCP 47]<span> (</span><span class='info'>Phillips, A. and M. Davis, &ldquo;Tags for Identifying Languages,&rdquo; .</span><span>)</span></a> language priority list,
represented as a comma-separated list of BCP 47 basic language ranges in descending priority order.
For instance, the value "fr-CA,fr-FR,en-CA" represents the preference for French spoken in Canada,
French spoken in France, followed by English spoken in Canada.
@@ -285,8 +291,8 @@ Language Preference</h3>
preference inferred by the user's IP address.
</p>
-<a name="anchor4"></a><br><hr>
-<table summary="layout" class="TOCbug" align="right" cellpadding="0" cellspacing="2"><tbody><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></tbody></table>
+<a name="anchor4"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
<a name="rfc.section.5"></a><h3>5.&nbsp;
Requesting Authentication in a Popup</h3>
@@ -312,7 +318,11 @@ Requesting Authentication in a Popup</h3>
<dt>openid.ui.mode</dt>
<dd>
REQUIRED. Value: "popup".
- New modes may be defined in future versions of this extension.
+ New modes may be defined in future versions of this extension. Any mode starting with the prefix "x-" should be
+ considered experimental. If an OpenID provider receives a request containing an experimental mode, and it does
+ not recognize that mode, it SHOULD NOT throw an error or invalidate further processing of this extension. If no other
+ parameters are present, then the OpenID provider receiving an experimental mode SHOULD continue processing the OpenID
+ request as if this extension were not included in it.
</dd>
</dl></blockquote><p>
@@ -347,13 +357,13 @@ Requesting Authentication in a Popup</h3>
</p>
<p>
The response to an authentication request in a popup is unchanged from
- <a class="info" href="#OpenID%202.0">[OpenID 2.0]<span> (</span><span class="info">OpenID 2.0 Workgroup, “OpenID 2.0,” .</span><span>)</span></a>.
+ <a class='info' href='#OpenID 2.0'>[OpenID 2.0]<span> (</span><span class='info'>OpenID 2.0 Workgroup, &ldquo;OpenID 2.0,&rdquo; .</span><span>)</span></a>.
Relying Parties detecting that the popup was closed without receiving an authentication response
SHOULD interpret the close event to be a negative assertion.
</p>
-<a name="anchor5"></a><br><hr>
-<table summary="layout" class="TOCbug" align="right" cellpadding="0" cellspacing="2"><tbody><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></tbody></table>
+<a name="anchor5"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
<a name="rfc.section.5.1"></a><h3>5.1.&nbsp;
Authentication Response in a Fragment</h3>
@@ -365,17 +375,66 @@ Authentication Response in a Fragment</h3>
URL in the authentication request. If the fragment delimiter character is present in the return_to URL,
the OpenID Provider SHOULD return the response parameters in the fragment portion of the URL. If the
return_to URL already contains a question mark "?", the first response parameter MUST be prefixed
- with an ampersand "&amp;", otherwise the first response parameter MUST be prefixed with a question mark "?".
+ with an ampersand "&", otherwise the first response parameter MUST be prefixed with a question mark "?".
</p>
-<a name="anchor6"></a><br><hr>
-<table summary="layout" class="TOCbug" align="right" cellpadding="0" cellspacing="2"><tbody><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></tbody></table>
+<a name="anchor6"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
<a name="rfc.section.6"></a><h3>6.&nbsp;
+Requesting Display of RP icons in the OP Approval UI</h3>
+
+<p>
+ When requesting authentication, the Relying Party MAY indicate to the OpenID Provider
+ the availability of graphical resources to represent the Relying Party brand at the OpenID Provider's approval UI.
+ This is indicated by including the following parameter:
+ </p>
+<blockquote class="text"><dl>
+<dt>openid.ui.icon</dt>
+<dd>
+ REQUIRED. Value: "true"
+
+</dd>
+</dl></blockquote><p>
+ In order to retrieve the indicated graphical resources, the OpenID Provider performs discovery on the Relying Party, as specified
+ in <a class='info' href='#OpenID 2.0'>[OpenID 2.0]<span> (</span><span class='info'>OpenID 2.0 Workgroup, &ldquo;OpenID 2.0,&rdquo; .</span><span>)</span></a> (or future versions of the OpenID protocol specification).
+ The RP SHOULD indicate the location of the graphical resource by adding an entry to its XRDS document:
+
+</p><div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre>
+
+&lt;Service xmlns="xri://$xrd*($v*2.0)"&gt;
+ &lt;Type&gt;http://specs.openid.net/extensions/ui/icon&lt;/Type&gt;
+ &lt;URI&gt;http://consumer.example.com/images/image.jpg&lt;/URI&gt;
+&lt;/Service&gt;
+
+&lt;Service xmlns="xri://$xrd*($v*2.0)"&gt;
+ &lt;Type&gt;http://specs.openid.net/extensions/ui/icon&lt;/Type&gt;
+ &lt;URI&gt;http://consumer.example.com/favicon.ico&lt;/URI&gt;
+&lt;/Service&gt;
+
+</pre></div>
+<p>
+ If the Relying Party indicates availability of graphical resources using the "icon" parameter but the OpenID Provider
+ does not succeed in obtaining a discovery document at the Relying Party, the OpenID Provider MAY attempt to locate a graphical
+ resource at the domain indicated by "openid.realm", under the path "/favicon.ico". If the realm contains the wildcard "*" for the host,
+ the OpenID Provider should replace it with "www".
+ In this case, the OpenID provider MAY restrict
+ the display of the resource to 16x16 format, and the Relying Party SHOULD ensure that the resource displays well in 16x16 format.
+
+</p>
+<p>
+ It is RECOMMENDED that the OpenID Provider do not inline graphical resources from the Relying Party without verification. Instead,
+ the OpenID Provider SHOULD proxy the icons after performing appropriate sanitization. Proxying is also necessary to avoid mixed-content
+ warnings if the OpenID Provider approval page is served over HTTPS but the graphical resource is only available over HTTP.
+
+</p>
+<a name="anchor7"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
+<a name="rfc.section.7"></a><h3>7.&nbsp;
Discovery</h3>
<p>
OpenID Providers supporting the User Interface Extension SHOULD advertise their support of the
- Extension using OpenID Discovery as defined in Section 7.3 of <a class="info" href="#OpenID%202.0">[OpenID 2.0]<span> (</span><span class="info">OpenID 2.0 Workgroup, “OpenID 2.0,” .</span><span>)</span></a>.
+ Extension using OpenID Discovery as defined in Section 7.3 of <a class='info' href='#OpenID 2.0'>[OpenID 2.0]<span> (</span><span class='info'>OpenID 2.0 Workgroup, &ldquo;OpenID 2.0,&rdquo; .</span><span>)</span></a>.
</p>
<p>
@@ -383,7 +442,8 @@ Discovery</h3>
http://specs.openid.net/extensions/ui/1.0/lang-pref as a &lt;xrd:Type&gt; child element of the &lt;xrd:Service&gt; element
in the XRDS discovery document.
-</p><div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre>&lt;Type&gt;http://specs.openid.net/extensions/ui/1.0/lang-pref&lt;/Type&gt;
+</p><div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre>
+&lt;Type&gt;http://specs.openid.net/extensions/ui/1.0/lang-pref&lt;/Type&gt;
</pre></div>
<p>
OpenID Providers supporting the popup functionality SHOULD define
@@ -391,11 +451,20 @@ Discovery</h3>
of the &lt;xrd:Service&gt; element in the
XRDS discovery document.
-</p><div style="display: table; width: 0pt; margin-left: 3em; margin-right: auto;"><pre>&lt;Type&gt;http://specs.openid.net/extensions/ui/1.0/mode/popup&lt;/Type&gt;
+</p><div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre>
+&lt;Type&gt;http://specs.openid.net/extensions/ui/1.0/mode/popup&lt;/Type&gt;
</pre></div>
-<a name="anchor7"></a><br><hr>
-<table summary="layout" class="TOCbug" align="right" cellpadding="0" cellspacing="2"><tbody><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></tbody></table>
-<a name="rfc.section.7"></a><h3>7.&nbsp;
+<p>
+ OpenID Providers supporting the graphical RP representation functionality SHOULD define
+ http://specs.openid.net/extensions/ui/1.0/icon as a &lt;xrd:Type&gt; child element of the &lt;xrd:Service&gt; element
+ in the XRDS discovery document.
+
+</p><div style='display: table; width: 0; margin-left: 3em; margin-right: auto'><pre>
+&lt;Type&gt;http://specs.openid.net/extensions/ui/1.0/icon&lt;/Type&gt;
+</pre></div>
+<a name="anchor8"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
+<a name="rfc.section.8"></a><h3>8.&nbsp;
Considerations</h3>
<p>
@@ -410,9 +479,9 @@ Considerations</h3>
window to protect the user's credentials and approval from clickjacking exploits.
</p>
-<a name="anchor8"></a><br><hr>
-<table summary="layout" class="TOCbug" align="right" cellpadding="0" cellspacing="2"><tbody><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></tbody></table>
-<a name="rfc.section.8"></a><h3>8.&nbsp;
+<a name="anchor9"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
+<a name="rfc.section.9"></a><h3>9.&nbsp;
Acknowledgements</h3>
<p>
@@ -435,27 +504,89 @@ Acknowledgements</h3>
David Recordon (david@sixapart.com)
</p>
-<a name="rfc.references1"></a><br><hr>
-<table summary="layout" class="TOCbug" align="right" cellpadding="0" cellspacing="2"><tbody><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></tbody></table>
-<h3>9.&nbsp;References</h3>
-<table border="0" width="99%">
-<tbody><tr><td class="author-text" valign="top"><a name="BCP 47">[BCP 47]</a></td>
-<td class="author-text">Phillips, A. and M. Davis, “Tags for Identifying Languages,” BCP&nbsp;47.</td></tr>
+<a name="rfc.references1"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
+<h3>10.&nbsp;References</h3>
+<table width="99%" border="0">
+<tr><td class="author-text" valign="top"><a name="BCP 47">[BCP 47]</a></td>
+<td class="author-text">Phillips, A. and M. Davis, &ldquo;Tags for Identifying Languages,&rdquo; BCP&nbsp;47.</td></tr>
<tr><td class="author-text" valign="top"><a name="Language Preference Attribute">[Language Preference Attribute]</a></td>
-<td class="author-text">axschema.org, “<a href="http://axschema.org/pref/language">Language Preference Attribute</a>.”</td></tr>
+<td class="author-text">axschema.org, &ldquo;<a href="http://axschema.org/pref/language">Language Preference Attribute</a>.&rdquo;</td></tr>
<tr><td class="author-text" valign="top"><a name="OpenID 2.0">[OpenID 2.0]</a></td>
-<td class="author-text">OpenID 2.0 Workgroup, “<a href="http://openid.net/">OpenID 2.0</a>.”</td></tr>
+<td class="author-text">OpenID 2.0 Workgroup, &ldquo;<a href="http://openid.net">OpenID 2.0</a>.&rdquo;</td></tr>
<tr><td class="author-text" valign="top"><a name="OpenID Attribute Exchange">[OpenID Attribute Exchange]</a></td>
-<td class="author-text">Hardt, D., Bufu, J., and J. Hoyt, “<a href="http://openid.net/specs/openid-attribute-exchange-1_0.html">OpenID Attribute Exchange 1.0</a>.”</td></tr>
+<td class="author-text">Hardt, D., Bufu, J., and J. Hoyt, &ldquo;<a href="http://openid.net/specs/openid-attribute-exchange-1_0.html">OpenID Attribute Exchange 1.0</a>.&rdquo;</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>
-</tbody></table>
+<td class="author-text">Bradner, B., &ldquo;<a href="http://tools.ietf.org/html/rfc2119">Key words for use in RFCs to Indicate Requirement Levels</a>,&rdquo; RFC&nbsp;2119.</td></tr>
+</table>
-<a name="rfc.authors"></a><br><hr>
-<table summary="layout" class="TOCbug" align="right" cellpadding="0" cellspacing="2"><tbody><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></tbody></table>
+<a name="anchor11"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
+<a name="rfc.section.A"></a><h3>Appendix A.&nbsp;
+Example Use of Experimental Mode</h3>
+
+<p>
+ In OpenID authentication, when using the checkid_immediate mode, there is no mechanism to indicate that there is a user logged in at the OpenID Provider.
+ Therefore, the Relying Party does not know if the checkid_immediate request failed because:
+ </p>
+<ol class="text">
+<li>The user does not have an account at the OpenID Provider (or is not logged in at the Provider), or:
+</li>
+<li>The user is logged in to the OpenID Provider but has not yet approved transparent login with the Relying Party.
+</li>
+</ol><p>
+ This makes it difficult for the RP to optimize the OpenID
+ user experience by, for instance, displaying a prominent button for the OpenID Provider in case (2). The following example shows how an experimental mode can be sent with
+ checkid_immediate requests to obtain this information.
+
+</p>
+<p>
+ </p>
+<blockquote class="text"><dl>
+<dt>openid.ns.ui</dt>
+<dd>
+ REQUIRED. Value: "http://specs.openid.net/extensions/ui/1.0"
+
+</dd>
+<dt>openid.ui.mode</dt>
+<dd>
+ REQUIRED. Value: "x-has-session".
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+<p>
+ To respond, the OpenID provider sends identical parameters in the "setup_needed" response to answer affirmatively (i.e., there IS an authenticated browser session):
+ </p>
+<blockquote class="text"><dl>
+<dt>openid.ns.ui</dt>
+<dd>
+ REQUIRED. Value: "http://specs.openid.net/extensions/ui/1.0"
+
+</dd>
+<dt>openid.ui.mode</dt>
+<dd>
+ REQUIRED. Value: "x-has-session".
+
+</dd>
+</dl></blockquote><p>
+ Alternative, if the OpenID provider needs to indicate the LACK of a session, it sends simply the UI namespace, without a mode, in the "setup_needed" response:
+ </p>
+<blockquote class="text"><dl>
+<dt>openid.ns.ui</dt>
+<dd>
+ REQUIRED. Value: "http://specs.openid.net/extensions/ui/1.0"
+
+</dd>
+</dl></blockquote><p>
+
+</p>
+<a name="rfc.authors"></a><br /><hr />
+<table summary="layout" cellpadding="0" cellspacing="2" class="TOCbug" align="right"><tr><td class="TOCbug"><a href="#toc">&nbsp;TOC&nbsp;</a></td></tr></table>
<h3>Authors' Addresses</h3>
-<table border="0" cellpadding="0" cellspacing="0" width="99%">
-<tbody><tr><td class="author-text">&nbsp;</td>
+<table width="99%" border="0" cellpadding="0" cellspacing="0">
+<tr><td class="author-text">&nbsp;</td>
<td class="author-text">Allen Tom</td></tr>
<tr><td class="author-text">&nbsp;</td>
<td class="author-text">Yahoo!</td></tr>
@@ -468,5 +599,5 @@ Acknowledgements</h3>
<td class="author-text">Google</td></tr>
<tr><td class="author" align="right">Email:&nbsp;</td>
<td class="author-text"><a href="mailto:breno@google.com">breno@google.com</a></td></tr>
-</tbody></table>
-</body></html> \ No newline at end of file
+</table>
+</body></html>
diff --git a/lib/Microsoft.Contracts.dll b/lib/Microsoft.Contracts.dll
index 012c506..fade5e1 100644
--- a/lib/Microsoft.Contracts.dll
+++ b/lib/Microsoft.Contracts.dll
Binary files differ
diff --git a/projecttemplates/MvcRelyingParty.vsixmanifest b/projecttemplates/MvcRelyingParty.vsixmanifest
index f401277..b14c99e 100644
--- a/projecttemplates/MvcRelyingParty.vsixmanifest
+++ b/projecttemplates/MvcRelyingParty.vsixmanifest
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<Vsix xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2010">
<Identifier Id="DotNetOpenAuth.MvcRelyingParty.15E8EC96-BDC3-47E2-8BB1-0483E9D16705">
- <Name>ASP.NET MVC OpenID web site (C#)</Name>
+ <Name>ASP.NET MVC 2 OpenID web site (C#)</Name>
<Author>DotNetOpenAuth</Author>
<Version>$version$</Version>
<Description>Resources for developing applications that use OpenID, OAuth, and InfoCard.</Description>
@@ -13,12 +13,12 @@
<InstalledByMsi>false</InstalledByMsi>
<SupportedProducts>
<VisualStudio Version="10.0">
- <Edition>VSTS</Edition>
- <Edition>VSTD</Edition>
<Edition>Pro</Edition>
+ <Edition>Premium</Edition>
+ <Edition>Ultimate</Edition>
</VisualStudio>
</SupportedProducts>
- <SupportedFrameworkRuntimeEdition MinVersion="3.5" MaxVersion="3.5" />
+ <SupportedFrameworkRuntimeEdition MinVersion="3.5" MaxVersion="4.0" />
</Identifier>
<References />
<Content>
diff --git a/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj b/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj
index 7097e07..540b702 100644
--- a/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj
+++ b/projecttemplates/MvcRelyingParty/MvcRelyingParty.csproj
@@ -52,13 +52,13 @@
<Reference Include="System.Data.Entity">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
- <Reference Include="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
+ <Reference Include="System.Web.Mvc" />
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.Web" />
- <Reference Include="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
+ <Reference Include="System.Web.Extensions" />
<Reference Include="System.Web.Abstractions" />
<Reference Include="System.Web.Routing" />
<Reference Include="System.Xml" />
diff --git a/projecttemplates/WebFormsRelyingParty.vsixmanifest b/projecttemplates/WebFormsRelyingParty.vsixmanifest
index 75a3646..6ebe392 100644
--- a/projecttemplates/WebFormsRelyingParty.vsixmanifest
+++ b/projecttemplates/WebFormsRelyingParty.vsixmanifest
@@ -13,12 +13,12 @@
<InstalledByMsi>false</InstalledByMsi>
<SupportedProducts>
<VisualStudio Version="10.0">
- <Edition>VSTS</Edition>
- <Edition>VSTD</Edition>
<Edition>Pro</Edition>
+ <Edition>Premium</Edition>
+ <Edition>Ultimate</Edition>
</VisualStudio>
</SupportedProducts>
- <SupportedFrameworkRuntimeEdition MinVersion="3.5" MaxVersion="3.5" />
+ <SupportedFrameworkRuntimeEdition MinVersion="3.5" MaxVersion="4.0" />
</Identifier>
<References />
<Content>
diff --git a/projecttemplates/projecttemplates.proj b/projecttemplates/projecttemplates.proj
index 0602362..470d1f3 100644
--- a/projecttemplates/projecttemplates.proj
+++ b/projecttemplates/projecttemplates.proj
@@ -209,7 +209,7 @@
<DowngradeProjects
Projects="@(ProjectTemplates2008Layout)"
Condition="'%(Extension)' == '.csproj'"
- DowngradeMvc2ToMvc1="true"
+ DowngradeMvc2ToMvc1="$(DowngradeMvc2ToMvc1)"
/>
<Purge Directories="$(ProjectTemplates2008LayoutPath)"
diff --git a/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj b/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj
index eab27db..41aff68 100644
--- a/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj
+++ b/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj
@@ -88,6 +88,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TwitterConsumer.cs" />
<Compile Include="Util.cs" />
+ <Compile Include="YubikeyRelyingParty.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj">
diff --git a/samples/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs b/samples/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs
index e83817a..8b769de 100644
--- a/samples/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs
+++ b/samples/DotNetOpenAuth.ApplicationBlock/InMemoryTokenManager.cs
@@ -10,6 +10,7 @@ namespace DotNetOpenAuth.ApplicationBlock {
using System.Diagnostics;
using DotNetOpenAuth.OAuth.ChannelElements;
using DotNetOpenAuth.OAuth.Messages;
+ using DotNetOpenAuth.OpenId.Extensions.OAuth;
/// <summary>
/// A token manager that only retains tokens in memory.
@@ -20,7 +21,7 @@ namespace DotNetOpenAuth.ApplicationBlock {
/// where the user only signs in without providing any authorization to access
/// Twitter APIs except to authenticate, since that access token is only useful once.
/// </remarks>
- public class InMemoryTokenManager : IConsumerTokenManager {
+ public class InMemoryTokenManager : IConsumerTokenManager, IOpenIdOAuthTokenManager {
private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();
/// <summary>
@@ -118,5 +119,25 @@ namespace DotNetOpenAuth.ApplicationBlock {
}
#endregion
+
+ #region IOpenIdOAuthTokenManager Members
+
+ /// <summary>
+ /// Stores a new request token obtained over an OpenID request.
+ /// </summary>
+ /// <param name="consumerKey">The consumer key.</param>
+ /// <param name="authorization">The authorization message carrying the request token and authorized access scope.</param>
+ /// <remarks>
+ /// <para>The token secret is the empty string.</para>
+ /// <para>Tokens stored by this method should be short-lived to mitigate
+ /// possible security threats. Their lifetime should be sufficient for the
+ /// relying party to receive the positive authentication assertion and immediately
+ /// send a follow-up request for the access token.</para>
+ /// </remarks>
+ public void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization) {
+ this.tokensAndSecrets[authorization.RequestToken] = String.Empty;
+ }
+
+ #endregion
}
} \ No newline at end of file
diff --git a/samples/DotNetOpenAuth.ApplicationBlock/YubikeyRelyingParty.cs b/samples/DotNetOpenAuth.ApplicationBlock/YubikeyRelyingParty.cs
new file mode 100644
index 0000000..6be749e
--- /dev/null
+++ b/samples/DotNetOpenAuth.ApplicationBlock/YubikeyRelyingParty.cs
@@ -0,0 +1,207 @@
+//-----------------------------------------------------------------------
+// <copyright file="YubikeyRelyingParty.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.ApplicationBlock {
+ using System;
+ using System.Collections.Specialized;
+ using System.Globalization;
+ using System.IO;
+ using System.Net;
+ using System.Text;
+ using System.Text.RegularExpressions;
+
+ /// <summary>
+ /// The set of possible results from verifying a Yubikey token.
+ /// </summary>
+ public enum YubikeyResult {
+ /// <summary>
+ /// The OTP is valid.
+ /// </summary>
+ Ok,
+
+ /// <summary>
+ /// The OTP is invalid format.
+ /// </summary>
+ BadOtp,
+
+ /// <summary>
+ /// The OTP has already been seen by the service.
+ /// </summary>
+ ReplayedOtp,
+
+ /// <summary>
+ /// The HMAC signature verification failed.
+ /// </summary>
+ /// <remarks>
+ /// This indicates a bug in the relying party code.
+ /// </remarks>
+ BadSignature,
+
+ /// <summary>
+ /// The request lacks a parameter.
+ /// </summary>
+ /// <remarks>
+ /// This indicates a bug in the relying party code.
+ /// </remarks>
+ MissingParameter,
+
+ /// <summary>
+ /// The request id does not exist.
+ /// </summary>
+ NoSuchClient,
+
+ /// <summary>
+ /// The request id is not allowed to verify OTPs.
+ /// </summary>
+ OperationNotAllowed,
+
+ /// <summary>
+ /// Unexpected error in our server. Please contact Yubico if you see this error.
+ /// </summary>
+ BackendError,
+ }
+
+ /// <summary>
+ /// Provides verification of a Yubikey one-time password (OTP) as a means of authenticating
+ /// a user at your web site or application.
+ /// </summary>
+ /// <remarks>
+ /// Please visit http://yubico.com/ for more information about this authentication method.
+ /// </remarks>
+ public class YubikeyRelyingParty {
+ /// <summary>
+ /// The default Yubico authorization server to use for validation and replay protection.
+ /// </summary>
+ private const string DefaultYubicoAuthorizationServer = "https://api.yubico.com/wsapi/verify";
+
+ /// <summary>
+ /// The format of the lines in the Yubico server response.
+ /// </summary>
+ private static readonly Regex ResultLineMatcher = new Regex(@"^(?<key>[^=]+)=(?<value>.*)$");
+
+ /// <summary>
+ /// The Yubico authorization server to use for validation and replay protection.
+ /// </summary>
+ private readonly string yubicoAuthorizationServer;
+
+ /// <summary>
+ /// The authorization ID assigned to your individual site by Yubico.
+ /// </summary>
+ private readonly int yubicoAuthorizationId;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="YubikeyRelyingParty"/> class
+ /// that uses the default Yubico server for validation and replay protection.
+ /// </summary>
+ /// <param name="authorizationId">The authorization ID assigned to your individual site by Yubico.
+ /// Get one from https://upgrade.yubico.com/getapikey/</param>
+ public YubikeyRelyingParty(int authorizationId)
+ : this(authorizationId, DefaultYubicoAuthorizationServer) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="YubikeyRelyingParty"/> class.
+ /// </summary>
+ /// <param name="authorizationId">The authorization ID assigned to your individual site by Yubico.
+ /// Contact tech@yubico.com if you haven't got an authId for your site.</param>
+ /// <param name="yubicoAuthorizationServer">The Yubico authorization server to use for validation and replay protection.</param>
+ public YubikeyRelyingParty(int authorizationId, string yubicoAuthorizationServer) {
+ if (authorizationId < 0) {
+ throw new ArgumentOutOfRangeException("authorizationId");
+ }
+
+ if (!Uri.IsWellFormedUriString(yubicoAuthorizationServer, UriKind.Absolute)) {
+ throw new ArgumentException("Invalid authorization server URI", "yubicoAuthorizationServer");
+ }
+
+ if (!yubicoAuthorizationServer.StartsWith(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) {
+ throw new ArgumentException("HTTPS is required for the Yubico server. HMAC response verification not supported.", "yubicoAuthorizationServer");
+ }
+
+ this.yubicoAuthorizationId = authorizationId;
+ this.yubicoAuthorizationServer = yubicoAuthorizationServer;
+ }
+
+ /// <summary>
+ /// Extracts the username out of a Yubikey token.
+ /// </summary>
+ /// <param name="yubikeyToken">The yubikey token.</param>
+ /// <returns>A 12 character string that is unique for this particular Yubikey device.</returns>
+ public static string ExtractUsername(string yubikeyToken) {
+ EnsureWellFormedToken(yubikeyToken);
+ return yubikeyToken.Substring(0, 12);
+ }
+
+ /// <summary>
+ /// Determines whether the specified yubikey token is valid and has not yet been used.
+ /// </summary>
+ /// <param name="yubikeyToken">The yubikey token.</param>
+ /// <returns>
+ /// <c>true</c> if the specified yubikey token is valid; otherwise, <c>false</c>.
+ /// </returns>
+ /// <exception cref="WebException">Thrown when the validity of the token could not be confirmed due to network issues.</exception>
+ public YubikeyResult IsValid(string yubikeyToken) {
+ EnsureWellFormedToken(yubikeyToken);
+
+ StringBuilder authorizationUri = new StringBuilder(this.yubicoAuthorizationServer);
+ authorizationUri.Append("?id=");
+ authorizationUri.Append(Uri.EscapeDataString(this.yubicoAuthorizationId.ToString(CultureInfo.InvariantCulture)));
+ authorizationUri.Append("&otp=");
+ authorizationUri.Append(Uri.EscapeDataString(yubikeyToken));
+
+ var request = WebRequest.Create(authorizationUri.ToString());
+ using (var response = request.GetResponse()) {
+ using (var responseReader = new StreamReader(response.GetResponseStream())) {
+ string line;
+ var result = new NameValueCollection();
+ while ((line = responseReader.ReadLine()) != null) {
+ Match m = ResultLineMatcher.Match(line);
+ if (m.Success) {
+ result[m.Groups["key"].Value] = m.Groups["value"].Value;
+ }
+ }
+
+ return ParseResult(result["status"]);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Parses the Yubico server result.
+ /// </summary>
+ /// <param name="status">The status field from the response.</param>
+ /// <returns>The enum value representing the result.</returns>
+ private static YubikeyResult ParseResult(string status) {
+ switch (status) {
+ case "OK": return YubikeyResult.Ok;
+ case "BAD_OTP": return YubikeyResult.BadOtp;
+ case "REPLAYED_OTP": return YubikeyResult.ReplayedOtp;
+ case "BAD_SIGNATURE": return YubikeyResult.BadSignature;
+ case "MISSING_PARAMETER": return YubikeyResult.MissingParameter;
+ case "NO_SUCH_CLIENT": return YubikeyResult.NoSuchClient;
+ case "OPERATION_NOT_ALLOWED": return YubikeyResult.OperationNotAllowed;
+ case "BACKEND_ERROR": return YubikeyResult.BackendError;
+ default: throw new ArgumentOutOfRangeException("status", status, "Unexpected status value.");
+ }
+ }
+
+ /// <summary>
+ /// Ensures the OTP is well formed.
+ /// </summary>
+ /// <param name="yubikeyToken">The yubikey token.</param>
+ private static void EnsureWellFormedToken(string yubikeyToken) {
+ if (yubikeyToken == null) {
+ throw new ArgumentNullException("yubikeyToken");
+ }
+
+ yubikeyToken = yubikeyToken.Trim();
+
+ if (yubikeyToken.Length <= 12) {
+ throw new ArgumentException("Yubikey token has unexpected length.");
+ }
+ }
+ }
+}
diff --git a/samples/OpenIdOfflineProvider/MainWindow.xaml b/samples/OpenIdOfflineProvider/MainWindow.xaml
index de215ba..5e9438f 100644
--- a/samples/OpenIdOfflineProvider/MainWindow.xaml
+++ b/samples/OpenIdOfflineProvider/MainWindow.xaml
@@ -6,7 +6,9 @@
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
+ <RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
+ <RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
@@ -20,6 +22,13 @@
<ComboBoxItem>Auto respond: No</ComboBoxItem>
<ComboBoxItem>Intercept</ComboBoxItem>
</ComboBox>
- <TextBox Height="auto" Margin="0,8,0,0" Grid.Row="2" Grid.ColumnSpan="2" Name="logBox" IsReadOnly="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto" />
+ <Expander Grid.Row="2" Grid.ColumnSpan="2" Header="Advanced options">
+ <StackPanel Margin="3">
+ <CheckBox Name="directedIdentityTrailingPeriodsCheckbox">Directed identity uses trailing periods in path</CheckBox>
+ <CheckBox Name="capitalizedHostName">Directed identity uses capitalized host name in claimed identifier</CheckBox>
+ </StackPanel>
+ </Expander>
+ <TextBox Height="auto" Margin="0,8,0,0" Grid.Row="3" Grid.ColumnSpan="2" Name="logBox" IsReadOnly="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto" />
+ <Button Grid.Row="4" Grid.ColumnSpan="2" Name="clearLogButton" Click="ClearLogButton_Click">Clear log</Button>
</Grid>
</Window>
diff --git a/samples/OpenIdOfflineProvider/MainWindow.xaml.cs b/samples/OpenIdOfflineProvider/MainWindow.xaml.cs
index 8f04da3..0fcac9c 100644
--- a/samples/OpenIdOfflineProvider/MainWindow.xaml.cs
+++ b/samples/OpenIdOfflineProvider/MainWindow.xaml.cs
@@ -25,6 +25,7 @@ namespace DotNetOpenAuth.OpenIdOfflineProvider {
using System.Windows.Navigation;
using System.Windows.Shapes;
using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId;
using DotNetOpenAuth.OpenId.Provider;
using log4net;
using log4net.Appender;
@@ -117,7 +118,15 @@ namespace DotNetOpenAuth.OpenIdOfflineProvider {
switch (checkidRequestList.SelectedIndex) {
case 0:
if (authRequest.IsDirectedIdentity) {
- authRequest.ClaimedIdentifier = new Uri(this.hostedProvider.UserIdentityPageBase, "directedidentity");
+ string userIdentityPageBase = this.hostedProvider.UserIdentityPageBase.AbsoluteUri;
+ if (capitalizedHostName.IsChecked.Value) {
+ userIdentityPageBase = (this.hostedProvider.UserIdentityPageBase.Scheme + Uri.SchemeDelimiter + this.hostedProvider.UserIdentityPageBase.Authority).ToUpperInvariant() + this.hostedProvider.UserIdentityPageBase.PathAndQuery;
+ }
+ string leafPath = "directedidentity";
+ if (directedIdentityTrailingPeriodsCheckbox.IsChecked.Value) {
+ leafPath += ".";
+ }
+ authRequest.ClaimedIdentifier = Identifier.Parse(userIdentityPageBase + leafPath, true);
authRequest.LocalIdentifier = authRequest.ClaimedIdentifier;
}
authRequest.IsAuthenticated = true;
@@ -169,5 +178,14 @@ namespace DotNetOpenAuth.OpenIdOfflineProvider {
MessageBox.Show(this, ex.Message, "Error while copying OP Identifier to the clipboard", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
+
+ /// <summary>
+ /// Handles the Click event of the ClearLogButton control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
+ private void ClearLogButton_Click(object sender, RoutedEventArgs e) {
+ logBox.Clear();
+ }
}
}
diff --git a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj
index a7a2296..72b6c14 100644
--- a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj
+++ b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj
@@ -44,19 +44,13 @@
<Reference Include="System.ComponentModel.DataAnnotations">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
- <Reference Include="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
- <HintPath>..\..\lib\System.Web.Mvc.dll</HintPath>
- </Reference>
+ <Reference Include="System.Web.Mvc" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
- <Reference Include="System.Web.Abstractions">
- <HintPath>..\..\lib\System.Web.Abstractions.dll</HintPath>
- </Reference>
- <Reference Include="System.Web.Routing">
- <HintPath>..\..\lib\System.Web.Routing.dll</HintPath>
- </Reference>
+ <Reference Include="System.Web.Abstractions" />
+ <Reference Include="System.Web.Routing" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Web.Services" />
diff --git a/samples/OpenIdProviderMvc/Web.config b/samples/OpenIdProviderMvc/Web.config
index 8f145b0..f8d2389 100644
--- a/samples/OpenIdProviderMvc/Web.config
+++ b/samples/OpenIdProviderMvc/Web.config
@@ -194,12 +194,12 @@
<runtime>
<legacyHMACWarning enabled="0" />
- <!-- If you target ASP.NET MVC 2, uncomment this so that MVC 1 components such as DotNetOpenAuth will work with it.
+ <!-- If you target ASP.NET MVC 2, uncomment this so that MVC 1 components such as DotNetOpenAuth will work with it. -->
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
- </assemblyBinding>-->
+ </assemblyBinding>
</runtime>
</configuration>
diff --git a/samples/OpenIdProviderWebForms/OpenIdProviderWebForms.csproj b/samples/OpenIdProviderWebForms/OpenIdProviderWebForms.csproj
index 603b372..4cd84c1 100644
--- a/samples/OpenIdProviderWebForms/OpenIdProviderWebForms.csproj
+++ b/samples/OpenIdProviderWebForms/OpenIdProviderWebForms.csproj
@@ -183,6 +183,10 @@
<Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project>
<Name>DotNetOpenAuth</Name>
</ProjectReference>
+ <ProjectReference Include="..\DotNetOpenAuth.ApplicationBlock\DotNetOpenAuth.ApplicationBlock.csproj">
+ <Project>{AA78D112-D889-414B-A7D4-467B34C7B663}</Project>
+ <Name>DotNetOpenAuth.ApplicationBlock</Name>
+ </ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
diff --git a/samples/OpenIdProviderWebForms/Web.config b/samples/OpenIdProviderWebForms/Web.config
index b76231b..de92621 100644
--- a/samples/OpenIdProviderWebForms/Web.config
+++ b/samples/OpenIdProviderWebForms/Web.config
@@ -62,6 +62,11 @@
<reporting enabled="true" />
</dotNetOpenAuth>
+ <appSettings>
+ <!-- Get your own Yubico API key here: https://upgrade.yubico.com/getapikey/ -->
+ <add key="YubicoAPIKey" value="3961"/>
+ </appSettings>
+
<system.web>
<!--
Set compilation debug="true" to insert debugging
diff --git a/samples/OpenIdProviderWebForms/decide.aspx.cs b/samples/OpenIdProviderWebForms/decide.aspx.cs
index b392d85..40c17c0 100644
--- a/samples/OpenIdProviderWebForms/decide.aspx.cs
+++ b/samples/OpenIdProviderWebForms/decide.aspx.cs
@@ -50,7 +50,12 @@ namespace OpenIdProviderWebForms {
this.profileFields.SetRequiredFieldsFromRequest(requestedFields);
if (!IsPostBack) {
var sregResponse = requestedFields.CreateResponse();
- sregResponse.Email = Membership.GetUser().Email;
+
+ // We MAY not have an entry for this user if they used Yubikey to log in.
+ MembershipUser user = Membership.GetUser();
+ if (user != null) {
+ sregResponse.Email = Membership.GetUser().Email;
+ }
this.profileFields.SetOpenIdProfileFields(sregResponse);
}
}
diff --git a/samples/OpenIdProviderWebForms/login.aspx b/samples/OpenIdProviderWebForms/login.aspx
index e8f42c5..f7898cc 100644
--- a/samples/OpenIdProviderWebForms/login.aspx
+++ b/samples/OpenIdProviderWebForms/login.aspx
@@ -14,4 +14,14 @@
<tr><td>bob3</td><td>test</td></tr>
<tr><td>bob4</td><td>test</td></tr>
</table>
+
+ <asp:Panel DefaultButton="yubicoButton" runat="server" style="margin-top: 25px" ID="yubicoPanel">
+ Login with Yubikey:
+ <asp:TextBox runat="server" type="text" ID="yubicoBox" ToolTip="Click here and press your Yubikey button."
+ style="background-image: url(http://yubico.com/favicon.ico); background-repeat: no-repeat; background-position: 0px 1px; padding-left: 18px; width: 20em;"
+ MaxLength="44" AutoCompleteType="Disabled" />
+ <asp:Button runat="server" ID="yubicoButton" Text="Login"
+ onclick="yubicoButton_Click" />
+ <asp:Label Text="[Yubikey Result]" runat="server" EnableViewState="false" Visible="false" ForeColor="Red" ID="yubikeyFailureLabel" />
+ </asp:Panel>
</asp:Content> \ No newline at end of file
diff --git a/samples/OpenIdProviderWebForms/login.aspx.cs b/samples/OpenIdProviderWebForms/login.aspx.cs
index 4051877..ef5b2c4 100644
--- a/samples/OpenIdProviderWebForms/login.aspx.cs
+++ b/samples/OpenIdProviderWebForms/login.aspx.cs
@@ -1,6 +1,10 @@
namespace OpenIdProviderWebForms {
using System;
+ using System.Configuration;
+ using System.Globalization;
+ using System.Web.Security;
using System.Web.UI.WebControls;
+ using DotNetOpenAuth.ApplicationBlock;
using DotNetOpenAuth.OpenId.Provider;
/// <summary>
@@ -9,6 +13,8 @@ namespace OpenIdProviderWebForms {
public partial class login : System.Web.UI.Page {
protected void Page_Load(object src, EventArgs e) {
if (!IsPostBack) {
+ this.yubicoPanel.Visible = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["YubicoAPIKey"]);
+
if (ProviderEndpoint.PendingAuthenticationRequest != null &&
!ProviderEndpoint.PendingAuthenticationRequest.IsDirectedIdentity) {
this.login1.UserName = Code.Util.ExtractUserName(
@@ -18,5 +24,27 @@ namespace OpenIdProviderWebForms {
}
}
}
+
+ protected void yubicoButton_Click(object sender, EventArgs e) {
+ string username;
+ if (this.TryVerifyYubikeyAndGetUsername(this.yubicoBox.Text, out username)) {
+ FormsAuthentication.RedirectFromLoginPage(username, false);
+ }
+ }
+
+ private bool TryVerifyYubikeyAndGetUsername(string token, out string username) {
+ var yubikey = new YubikeyRelyingParty(int.Parse(ConfigurationManager.AppSettings["YubicoAPIKey"], CultureInfo.InvariantCulture));
+ YubikeyResult result = yubikey.IsValid(token);
+ switch (result) {
+ case YubikeyResult.Ok:
+ username = YubikeyRelyingParty.ExtractUsername(token);
+ return true;
+ default:
+ this.yubikeyFailureLabel.Visible = true;
+ this.yubikeyFailureLabel.Text = result.ToString();
+ username = null;
+ return false;
+ }
+ }
}
} \ No newline at end of file
diff --git a/samples/OpenIdProviderWebForms/login.aspx.designer.cs b/samples/OpenIdProviderWebForms/login.aspx.designer.cs
index 83826a1..307dd96 100644
--- a/samples/OpenIdProviderWebForms/login.aspx.designer.cs
+++ b/samples/OpenIdProviderWebForms/login.aspx.designer.cs
@@ -1,10 +1,9 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:2.0.50727.3521
//
// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
+// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
@@ -21,5 +20,41 @@ namespace OpenIdProviderWebForms {
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Login login1;
+
+ /// <summary>
+ /// yubicoPanel control.
+ /// </summary>
+ /// <remarks>
+ /// Auto-generated field.
+ /// To modify move field declaration from designer file to code-behind file.
+ /// </remarks>
+ protected global::System.Web.UI.WebControls.Panel yubicoPanel;
+
+ /// <summary>
+ /// yubicoBox control.
+ /// </summary>
+ /// <remarks>
+ /// Auto-generated field.
+ /// To modify move field declaration from designer file to code-behind file.
+ /// </remarks>
+ protected global::System.Web.UI.WebControls.TextBox yubicoBox;
+
+ /// <summary>
+ /// yubicoButton control.
+ /// </summary>
+ /// <remarks>
+ /// Auto-generated field.
+ /// To modify move field declaration from designer file to code-behind file.
+ /// </remarks>
+ protected global::System.Web.UI.WebControls.Button yubicoButton;
+
+ /// <summary>
+ /// yubikeyFailureLabel control.
+ /// </summary>
+ /// <remarks>
+ /// Auto-generated field.
+ /// To modify move field declaration from designer file to code-behind file.
+ /// </remarks>
+ protected global::System.Web.UI.WebControls.Label yubikeyFailureLabel;
}
}
diff --git a/samples/OpenIdRelyingPartyMvc/OpenIdRelyingPartyMvc.csproj b/samples/OpenIdRelyingPartyMvc/OpenIdRelyingPartyMvc.csproj
index a94bd88..a3c7b78 100644
--- a/samples/OpenIdRelyingPartyMvc/OpenIdRelyingPartyMvc.csproj
+++ b/samples/OpenIdRelyingPartyMvc/OpenIdRelyingPartyMvc.csproj
@@ -43,19 +43,13 @@
<Reference Include="System.ComponentModel.DataAnnotations">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
- <Reference Include="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
- <HintPath>..\..\lib\System.Web.Mvc.dll</HintPath>
- </Reference>
+ <Reference Include="System.Web.Mvc" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
- <Reference Include="System.Web.Abstractions">
- <HintPath>..\..\lib\System.Web.Abstractions.dll</HintPath>
- </Reference>
- <Reference Include="System.Web.Routing">
- <HintPath>..\..\lib\System.Web.Routing.dll</HintPath>
- </Reference>
+ <Reference Include="System.Web.Abstractions" />
+ <Reference Include="System.Web.Routing" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Web.Services" />
diff --git a/samples/OpenIdRelyingPartyMvc/Web.config b/samples/OpenIdRelyingPartyMvc/Web.config
index 6aba949..0585cf5 100644
--- a/samples/OpenIdRelyingPartyMvc/Web.config
+++ b/samples/OpenIdRelyingPartyMvc/Web.config
@@ -171,12 +171,12 @@
<runtime>
<legacyHMACWarning enabled="0" />
- <!-- If you target ASP.NET MVC 2, uncomment this so that MVC 1 components such as DotNetOpenAuth will work with it.
+ <!-- If you target ASP.NET MVC 2, uncomment this so that MVC 1 components such as DotNetOpenAuth will work with it. -->
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
- </assemblyBinding>-->
+ </assemblyBinding>
</runtime>
</configuration>
diff --git a/src/DotNetOpenAuth.Test/Logging.config b/src/DotNetOpenAuth.Test/Logging.config
index cd19de2..87da027 100644
--- a/src/DotNetOpenAuth.Test/Logging.config
+++ b/src/DotNetOpenAuth.Test/Logging.config
@@ -30,7 +30,4 @@
<logger name="DotNetOpenAuth.Test">
<level value="Debug" />
</logger>
- <logger name="DotNetOpenAuth.OpenId.ChannelElements.SigningBindingElement">
- <level value="WARN" />
- </logger>
</log4net>
diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs
index 8d5ef2a..6160680 100644
--- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs
@@ -21,7 +21,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements {
/// </summary>
[TestCase]
public void SignaturesMatchKnownGood() {
- Protocol protocol = Protocol.Default;
+ Protocol protocol = Protocol.V20;
var settings = new ProviderSecuritySettings();
var store = new AssociationMemoryStore<AssociationRelyingPartyType>();
byte[] associationSecret = Convert.FromBase64String("rsSwv1zPWfjPRQU80hciu8FPDC+GONAMJQ/AvSo1a2M=");
diff --git a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs
index 811b7d1..25b0607 100644
--- a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs
@@ -12,6 +12,7 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty {
using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
using DotNetOpenAuth.OpenId.Messages;
using DotNetOpenAuth.OpenId.RelyingParty;
+ using DotNetOpenAuth.Test.Mocks;
using NUnit.Framework;
[TestFixture]
@@ -120,6 +121,26 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty {
Assert.IsNull(authResponse.GetCallbackArgument("a"));
}
+ /// <summary>
+ /// Verifies that certain problematic claimed identifiers pass through to the RP response correctly.
+ /// </summary>
+ [TestCase]
+ public void ProblematicClaimedId() {
+ var providerEndpoint = new ProviderEndpointDescription(OpenIdTestBase.OPUri, Protocol.Default.Version);
+ string claimed_id = BaseMockUri + "a./b.";
+ var se = IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimed_id, claimed_id, providerEndpoint, null, null);
+ UriIdentifier identityUri = (UriIdentifier)se.ClaimedIdentifier;
+ var mockId = new MockIdentifier(identityUri, this.MockResponder, new IdentifierDiscoveryResult[] { se });
+
+ var positiveAssertion = this.GetPositiveAssertion();
+ positiveAssertion.ClaimedIdentifier = mockId;
+ positiveAssertion.LocalIdentifier = mockId;
+ var rp = CreateRelyingParty();
+ var authResponse = new PositiveAuthenticationResponse(positiveAssertion, rp);
+ Assert.AreEqual(AuthenticationStatus.Authenticated, authResponse.Status);
+ Assert.AreEqual(claimed_id, authResponse.ClaimedIdentifier.ToString());
+ }
+
private PositiveAssertionResponse GetPositiveAssertion() {
return this.GetPositiveAssertion(false);
}
diff --git a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs
index 73c185e..5b015ff 100644
--- a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs
@@ -112,22 +112,51 @@ namespace DotNetOpenAuth.Test.OpenId {
Identifier noFragment = UriIdentifier.Parse("http://a/b");
Identifier fragment = UriIdentifier.Parse("http://a/b#c");
Assert.AreSame(noFragment, noFragment.TrimFragment());
- Assert.AreEqual(noFragment, fragment.TrimFragment());
+ Assert.AreEqual(noFragment.ToString(), fragment.TrimFragment().ToString());
+
+ // Try the problematic ones
+ TestAsFullAndPartialTrust(fullTrust => {
+ Identifier noFrag = UriIdentifier.Parse("http://a/b./c");
+ Identifier frag = UriIdentifier.Parse("http://a/b./c#d");
+ Assert.AreSame(noFrag, noFrag.TrimFragment());
+ Assert.AreEqual(noFrag.ToString(), frag.TrimFragment().ToString());
+ });
}
[TestCase]
public void ToStringTest() {
Assert.AreEqual(this.goodUri, new UriIdentifier(this.goodUri).ToString());
+ TestAsFullAndPartialTrust(fullTrust => {
+ Assert.AreEqual("http://abc/D./e.?Qq#Ff", new UriIdentifier("HTTP://ABC/D./e.?Qq#Ff").ToString());
+ Assert.AreEqual("http://abc/D./e.?Qq", new UriIdentifier("HTTP://ABC/D./e.?Qq").ToString());
+ Assert.AreEqual("http://abc/D./e.#Ff", new UriIdentifier("HTTP://ABC/D./e.#Ff").ToString());
+ Assert.AreEqual("http://abc/", new UriIdentifier("HTTP://ABC").ToString());
+ Assert.AreEqual("http://abc/?q", new UriIdentifier("HTTP://ABC?q").ToString());
+ Assert.AreEqual("http://abc/#f", new UriIdentifier("HTTP://ABC#f").ToString());
+ });
}
[TestCase]
public void EqualsTest() {
- Assert.AreEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri));
- // This next test is an interesting side-effect of passing off to Uri.Equals. But it's probably ok.
- Assert.AreEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri + "#frag"));
- Assert.AreNotEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri + "a"));
- Assert.AreNotEqual(null, new UriIdentifier(this.goodUri));
- Assert.IsTrue(new UriIdentifier(this.goodUri).Equals(this.goodUri));
+ TestAsFullAndPartialTrust(fulltrust => {
+ Assert.AreEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri));
+ // This next test is an interesting side-effect of passing off to Uri.Equals. But it's probably ok.
+ Assert.AreEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri + "#frag"));
+ Assert.AreEqual(new UriIdentifier("http://a/b./c."), new UriIdentifier("http://a/b./c.#frag"));
+ Assert.AreNotEqual(new UriIdentifier(this.goodUri), new UriIdentifier(this.goodUri + "a"));
+ Assert.AreNotEqual(null, new UriIdentifier(this.goodUri));
+ Assert.IsTrue(new UriIdentifier(this.goodUri).Equals(this.goodUri));
+
+ Assert.AreEqual(Identifier.Parse("HTTP://WWW.FOO.COM/abc", true), Identifier.Parse("http://www.foo.com/abc", true));
+ Assert.AreEqual(Identifier.Parse("HTTP://WWW.FOO.COM/abc", true), Identifier.Parse("http://www.foo.com/abc", false));
+ Assert.AreEqual(Identifier.Parse("HTTP://WWW.FOO.COM/abc", false), Identifier.Parse("http://www.foo.com/abc", false));
+ Assert.AreNotEqual(Identifier.Parse("http://www.foo.com/abc", true), Identifier.Parse("http://www.foo.com/ABC", true));
+ Assert.AreNotEqual(Identifier.Parse("http://www.foo.com/abc", true), Identifier.Parse("http://www.foo.com/ABC", false));
+ Assert.AreNotEqual(Identifier.Parse("http://www.foo.com/abc", false), Identifier.Parse("http://www.foo.com/ABC", false));
+
+ Assert.AreNotEqual(Identifier.Parse("http://a/b./c."), Identifier.Parse("http://a/b/c."));
+ Assert.AreEqual(Identifier.Parse("http://a/b./c."), Identifier.Parse("http://a/b./c."));
+ });
}
[TestCase]
@@ -150,6 +179,51 @@ namespace DotNetOpenAuth.Test.OpenId {
Assert.AreEqual("https://host:80/PaTH?KeY=VaLUE#fRag", id.ToString());
}
+ /// <summary>
+ /// Verifies that URIs that contain base64 encoded path segments (such as Yahoo) are properly preserved.
+ /// </summary>
+ /// <remarks>
+ /// Yahoo includes a base64 encoded part as their last path segment,
+ /// which may end with a period. The default .NET Uri parser trims off
+ /// trailing periods, which breaks OpenID unless special precautions are taken.
+ /// </remarks>
+ [TestCase]
+ public void TrailingPeriodsNotTrimmed() {
+ TestAsFullAndPartialTrust(fullTrust => {
+ string claimedIdentifier = "https://me.yahoo.com/a/AsDf.#asdf";
+ Identifier id = claimedIdentifier;
+ Assert.AreEqual(claimedIdentifier, id.OriginalString);
+ Assert.AreEqual(claimedIdentifier, id.ToString());
+
+ UriIdentifier idUri = new UriIdentifier(claimedIdentifier);
+ Assert.AreEqual(claimedIdentifier, idUri.OriginalString);
+ Assert.AreEqual(claimedIdentifier, idUri.ToString());
+ if (fullTrust) {
+ Assert.AreEqual(claimedIdentifier, idUri.Uri.AbsoluteUri);
+ }
+ Assert.AreEqual(Uri.UriSchemeHttps, idUri.Uri.Scheme); // in case custom scheme tricks are played, this must still match
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().ToString());
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().OriginalString);
+ Assert.AreEqual(id.ToString(), new UriIdentifier((Uri)idUri).ToString(), "Round tripping UriIdentifier->Uri->UriIdentifier failed.");
+
+ idUri = new UriIdentifier(new Uri(claimedIdentifier));
+ Assert.AreEqual(claimedIdentifier, idUri.OriginalString);
+ Assert.AreEqual(claimedIdentifier, idUri.ToString());
+ if (fullTrust) {
+ Assert.AreEqual(claimedIdentifier, idUri.Uri.AbsoluteUri);
+ }
+ Assert.AreEqual(Uri.UriSchemeHttps, idUri.Uri.Scheme); // in case custom scheme tricks are played, this must still match
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().ToString());
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.", idUri.TrimFragment().OriginalString);
+ Assert.AreEqual(id.ToString(), new UriIdentifier((Uri)idUri).ToString(), "Round tripping UriIdentifier->Uri->UriIdentifier failed.");
+
+ claimedIdentifier = "https://me.yahoo.com:443/a/AsDf.#asdf";
+ id = claimedIdentifier;
+ Assert.AreEqual(claimedIdentifier, id.OriginalString);
+ Assert.AreEqual("https://me.yahoo.com/a/AsDf.#asdf", id.ToString());
+ });
+ }
+
[TestCase]
public void HttpSchemePrepended() {
UriIdentifier id = new UriIdentifier("www.yahoo.com");
@@ -193,5 +267,45 @@ namespace DotNetOpenAuth.Test.OpenId {
Assert.AreEqual("http://www.yahoo.com/", secureId.ToString());
Assert.AreEqual(0, Discover(secureId).Count());
}
+
+ /// <summary>
+ /// Verifies that unicode hostnames are handled.
+ /// </summary>
+ [TestCase]
+ public void UnicodeHostSupport() {
+ var id = new UriIdentifier("http://server崎/村");
+ Assert.AreEqual("server崎", id.Uri.Host);
+ }
+
+ /// <summary>
+ /// Verifies SimpleUri behavior
+ /// </summary>
+ [TestCase]
+ public void SimpleUri() {
+ Assert.AreEqual("http://abc/D./e.?Qq#Ff", new UriIdentifier.SimpleUri("HTTP://ABC/D./e.?Qq#Ff").ToString());
+ Assert.AreEqual("http://abc/D./e.?Qq", new UriIdentifier.SimpleUri("HTTP://ABC/D./e.?Qq").ToString());
+ Assert.AreEqual("http://abc/D./e.#Ff", new UriIdentifier.SimpleUri("HTTP://ABC/D./e.#Ff").ToString());
+ Assert.AreEqual("http://abc/", new UriIdentifier.SimpleUri("HTTP://ABC/").ToString());
+ Assert.AreEqual("http://abc/", new UriIdentifier.SimpleUri("HTTP://ABC").ToString());
+ Assert.AreEqual("http://abc/?q", new UriIdentifier.SimpleUri("HTTP://ABC?q").ToString());
+ Assert.AreEqual("http://abc/#f", new UriIdentifier.SimpleUri("HTTP://ABC#f").ToString());
+
+ Assert.AreEqual("http://abc/a//b", new UriIdentifier.SimpleUri("http://abc/a//b").ToString());
+ Assert.AreEqual("http://abc/a%2Fb/c", new UriIdentifier.SimpleUri("http://abc/a%2fb/c").ToString());
+ Assert.AreEqual("http://abc/A/c", new UriIdentifier.SimpleUri("http://abc/%41/c").ToString());
+ }
+
+ private static void TestAsFullAndPartialTrust(Action<bool> action) {
+ // Test a bunch of interesting URLs both with scheme substitution on and off.
+ Assert.IsTrue(UriIdentifier_Accessor.schemeSubstitution, "Expected scheme substitution to be working.");
+ action(true);
+
+ UriIdentifier_Accessor.schemeSubstitution = false;
+ try {
+ action(false);
+ } finally {
+ UriIdentifier_Accessor.schemeSubstitution = true;
+ }
+ }
}
}
diff --git a/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs b/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs
index 37f9c78..980d90f 100644
--- a/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs
+++ b/src/DotNetOpenAuth/ComponentModel/ConverterBase.cs
@@ -144,6 +144,7 @@ using System.Reflection;
/// The conversion cannot be performed.
/// </exception>
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
+ Contract.Assume(destinationType != null, "Missing contract.");
if (destinationType.IsInstanceOfType(value)) {
return value;
}
diff --git a/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs b/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs
index 6ba9c4b..61c0fd8 100644
--- a/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs
+++ b/src/DotNetOpenAuth/ComponentModel/IdentifierConverter.cs
@@ -45,7 +45,7 @@ namespace DotNetOpenAuth.ComponentModel {
return null;
}
- MemberInfo identifierParse = typeof(Identifier).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public);
+ MemberInfo identifierParse = typeof(Identifier).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(string) }, null);
return CreateInstanceDescriptor(identifierParse, new object[] { value.ToString() });
}
diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd
index a4f932e..3164ec5 100644
--- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd
+++ b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd
@@ -319,7 +319,24 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
- <xs:attribute name="allowDualPurposeIdentifiers" type="xs:boolean" />
+ <xs:attribute name="allowDualPurposeIdentifiers" type="xs:boolean">
+ <xs:annotation>
+ <xs:documentation>
+ Controls whether identifiers that are both OP Identifiers and Claimed Identifiers
+ should ever be recognized as claimed identifiers.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="allowApproximateIdentifierDiscovery" type="xs:boolean">
+ <xs:annotation>
+ <xs:documentation>
+ Controls whether certain Claimed Identifiers that exploit
+ features that .NET does not have the ability to send exact HTTP requests for will
+ still be allowed by using an approximate HTTP request.
+ Only impacts hosts running under partial trust.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
<xs:attribute name="protectDownlevelReplayAttacks" type="xs:boolean">
<xs:annotation>
<xs:documentation>
diff --git a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
index 1e3df8f..1bf2ebc 100644
--- a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
+++ b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
@@ -71,6 +71,11 @@ namespace DotNetOpenAuth.Configuration {
private const string AllowDualPurposeIdentifiersConfigName = "allowDualPurposeIdentifiers";
/// <summary>
+ /// Gets the name of the @allowApproximateIdentifierDiscovery attribute.
+ /// </summary>
+ private const string AllowApproximateIdentifierDiscoveryConfigName = "allowApproximateIdentifierDiscovery";
+
+ /// <summary>
/// Gets the name of the @protectDownlevelReplayAttacks attribute.
/// </summary>
private const string ProtectDownlevelReplayAttacksConfigName = "protectDownlevelReplayAttacks";
@@ -206,6 +211,20 @@ namespace DotNetOpenAuth.Configuration {
}
/// <summary>
+ /// Gets or sets a value indicating whether certain Claimed Identifiers that exploit
+ /// features that .NET does not have the ability to send exact HTTP requests for will
+ /// still be allowed by using an approximate HTTP request.
+ /// </summary>
+ /// <value>
+ /// The default value is <c>true</c>.
+ /// </value>
+ [ConfigurationProperty(AllowApproximateIdentifierDiscoveryConfigName, DefaultValue = true)]
+ public bool AllowApproximateIdentifierDiscovery {
+ get { return (bool)this[AllowApproximateIdentifierDiscoveryConfigName]; }
+ set { this[AllowApproximateIdentifierDiscoveryConfigName] = value; }
+ }
+
+ /// <summary>
/// Gets or sets a value indicating whether the Relying Party should take special care
/// to protect users against replay attacks when interoperating with OpenID 1.1 Providers.
/// </summary>
@@ -234,6 +253,7 @@ namespace DotNetOpenAuth.Configuration {
settings.RejectDelegatingIdentifiers = this.RejectDelegatingIdentifiers;
settings.IgnoreUnsignedExtensions = this.IgnoreUnsignedExtensions;
settings.AllowDualPurposeIdentifiers = this.AllowDualPurposeIdentifiers;
+ settings.AllowApproximateIdentifierDiscovery = this.AllowApproximateIdentifierDiscovery;
settings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks;
return settings;
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index 3dee328..1c018eb 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -163,7 +163,7 @@ http://opensource.org/licenses/ms-pl.html
<CodeContractsEmitXMLDocs>True</CodeContractsEmitXMLDocs>
<CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions>
<CodeContractsReferenceAssembly>Build</CodeContractsReferenceAssembly>
- <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
+ <CodeAnalysisRuleSet>Migrated rules for DotNetOpenAuth.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL">
@@ -736,7 +736,7 @@ http://opensource.org/licenses/ms-pl.html
<ItemGroup>
<SignDependsOn Include="BuildUnifiedProduct" />
<DelaySignedAssemblies Include="$(ILMergeOutputAssembly);
- $(OutputPath)$(ProductName).Contracts.dll;
+ $(OutputPath)CodeContracts\$(ProductName).Contracts.dll;
" />
</ItemGroup>
<PropertyGroup>
diff --git a/src/DotNetOpenAuth/GlobalSuppressions.cs b/src/DotNetOpenAuth/GlobalSuppressions.cs
index e436846..9b1bcfa 100644
--- a/src/DotNetOpenAuth/GlobalSuppressions.cs
+++ b/src/DotNetOpenAuth/GlobalSuppressions.cs
@@ -57,3 +57,6 @@
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform.#DotNetOpenAuth.OpenId.Provider.IProviderBehavior.OnIncomingRequest(DotNetOpenAuth.OpenId.Provider.IRequest)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform.#DotNetOpenAuth.OpenId.Provider.IProviderBehavior.ApplySecuritySettings(DotNetOpenAuth.OpenId.Provider.ProviderSecuritySettings)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2243:AttributeStringLiteralsShouldParseCorrectly")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.Mvc")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Mvc", Scope = "namespace", Target = "DotNetOpenAuth.Mvc")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Portability", "CA1903:UseOnlyApiFromTargetedFramework", MessageId = "System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs
index 86c1118..ae45229 100644
--- a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs
+++ b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs
@@ -268,6 +268,7 @@ namespace DotNetOpenAuth.InfoCard {
[Category(InfoCardCategory), DefaultValue(PrivacyUrlDefault)]
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "We construct a Uri to validate the format of the string.")]
[SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "That overload is NOT the same.")]
+ [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "This can take ~/ paths.")]
public string PrivacyUrl {
get {
return (string)this.ViewState[PrivacyUrlViewStateKey] ?? PrivacyUrlDefault;
@@ -570,24 +571,28 @@ namespace DotNetOpenAuth.InfoCard {
Panel supportedPanel = new Panel();
- if (!this.DesignMode) {
- // At the user agent, assume InfoCard is not supported until
- // the JavaScript discovers otherwise and reveals this panel.
- supportedPanel.Style[HtmlTextWriterStyle.Display] = "none";
- }
+ try {
+ if (!this.DesignMode) {
+ // At the user agent, assume InfoCard is not supported until
+ // the JavaScript discovers otherwise and reveals this panel.
+ supportedPanel.Style[HtmlTextWriterStyle.Display] = "none";
+ }
- supportedPanel.Controls.Add(this.CreateInfoCardImage());
+ supportedPanel.Controls.Add(this.CreateInfoCardImage());
- // trigger the selector at page load?
- if (this.AutoPopup && !this.Page.IsPostBack) {
- this.Page.ClientScript.RegisterStartupScript(
- typeof(InfoCardSelector),
- "selector_load_trigger",
- this.GetInfoCardSelectorActivationScript(true),
- true);
+ // trigger the selector at page load?
+ if (this.AutoPopup && !this.Page.IsPostBack) {
+ this.Page.ClientScript.RegisterStartupScript(
+ typeof(InfoCardSelector),
+ "selector_load_trigger",
+ this.GetInfoCardSelectorActivationScript(true),
+ true);
+ }
+ return supportedPanel;
+ } catch {
+ supportedPanel.Dispose();
+ throw;
}
-
- return supportedPanel;
}
/// <summary>
@@ -624,10 +629,15 @@ namespace DotNetOpenAuth.InfoCard {
Contract.Ensures(Contract.Result<Panel>() != null);
Panel unsupportedPanel = new Panel();
- if (this.UnsupportedTemplate != null) {
- this.UnsupportedTemplate.InstantiateIn(unsupportedPanel);
+ try {
+ if (this.UnsupportedTemplate != null) {
+ this.UnsupportedTemplate.InstantiateIn(unsupportedPanel);
+ }
+ return unsupportedPanel;
+ } catch {
+ unsupportedPanel.Dispose();
+ throw;
}
- return unsupportedPanel;
}
/// <summary>
@@ -692,13 +702,18 @@ namespace DotNetOpenAuth.InfoCard {
private Image CreateInfoCardImage() {
// add clickable image
Image image = new Image();
- image.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(typeof(InfoCardSelector), InfoCardImage.GetImageManifestResourceStreamName(this.ImageSize));
- image.AlternateText = InfoCardStrings.SelectorClickPrompt;
- image.ToolTip = this.ToolTip;
- image.Style[HtmlTextWriterStyle.Cursor] = "hand";
-
- image.Attributes["onclick"] = this.GetInfoCardSelectorActivationScript(false);
- return image;
+ try {
+ image.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(typeof(InfoCardSelector), InfoCardImage.GetImageManifestResourceStreamName(this.ImageSize));
+ image.AlternateText = InfoCardStrings.SelectorClickPrompt;
+ image.ToolTip = this.ToolTip;
+ image.Style[HtmlTextWriterStyle.Cursor] = "hand";
+
+ image.Attributes["onclick"] = this.GetInfoCardSelectorActivationScript(false);
+ return image;
+ } catch {
+ image.Dispose();
+ throw;
+ }
}
/// <summary>
diff --git a/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs b/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs
index 124f9f8..2ac2b7e 100644
--- a/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs
+++ b/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs
@@ -74,7 +74,13 @@ namespace DotNetOpenAuth.InfoCard {
public void AddDecryptingToken(X509Certificate2 certificate) {
Contract.Requires<ArgumentNullException>(certificate != null);
Contract.Requires<ArgumentException>(certificate.HasPrivateKey);
- this.AddDecryptingToken(new X509SecurityToken(certificate));
+ var cert = new X509SecurityToken(certificate);
+ try {
+ this.AddDecryptingToken(cert);
+ } catch {
+ cert.Dispose();
+ throw;
+ }
}
#if CONTRACTS_FULL
diff --git a/src/DotNetOpenAuth/InfoCard/Token/Token.cs b/src/DotNetOpenAuth/InfoCard/Token/Token.cs
index 7fa9a95..89fa3a3 100644
--- a/src/DotNetOpenAuth/InfoCard/Token/Token.cs
+++ b/src/DotNetOpenAuth/InfoCard/Token/Token.cs
@@ -49,16 +49,18 @@ namespace DotNetOpenAuth.InfoCard {
byte[] decryptedBytes;
string decryptedString;
- using (XmlReader tokenReader = XmlReader.Create(new StringReader(tokenXml))) {
- Contract.Assume(tokenReader != null); // BCL contract should say XmlReader.Create result != null
- if (IsEncrypted(tokenReader)) {
- Logger.InfoCard.DebugFormat("Incoming SAML token, before decryption: {0}", tokenXml);
- decryptedBytes = decryptor.DecryptToken(tokenReader);
- decryptedString = Encoding.UTF8.GetString(decryptedBytes);
- Contract.Assume(decryptedString != null); // BCL contracts should be enhanced here
- } else {
- decryptedBytes = Encoding.UTF8.GetBytes(tokenXml);
- decryptedString = tokenXml;
+ using (StringReader xmlReader = new StringReader(tokenXml)) {
+ using (XmlReader tokenReader = XmlReader.Create(xmlReader)) {
+ Contract.Assume(tokenReader != null); // BCL contract should say XmlReader.Create result != null
+ if (IsEncrypted(tokenReader)) {
+ Logger.InfoCard.DebugFormat("Incoming SAML token, before decryption: {0}", tokenXml);
+ decryptedBytes = decryptor.DecryptToken(tokenReader);
+ decryptedString = Encoding.UTF8.GetString(decryptedBytes);
+ Contract.Assume(decryptedString != null); // BCL contracts should be enhanced here
+ } else {
+ decryptedBytes = Encoding.UTF8.GetBytes(tokenXml);
+ decryptedString = tokenXml;
+ }
}
}
diff --git a/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs b/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs
index 48b7794..4ac871a 100644
--- a/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs
+++ b/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs
@@ -226,7 +226,9 @@ namespace DotNetOpenAuth.InfoCard {
int charMapLength = charMap.Length;
byte[] raw = Convert.FromBase64String(ppid);
- raw = SHA1.Create().ComputeHash(raw);
+ using (HashAlgorithm hasher = SHA1.Create()) {
+ raw = hasher.ComputeHash(raw);
+ }
StringBuilder callSign = new StringBuilder();
diff --git a/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs b/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs
index dd34d90..c9bc1d3 100644
--- a/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs
+++ b/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs
@@ -90,11 +90,16 @@ namespace DotNetOpenAuth.Messaging {
public override StreamReader GetResponseReader() {
this.ResponseStream.Seek(0, SeekOrigin.Begin);
string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding];
- if (string.IsNullOrEmpty(contentEncoding)) {
- return new StreamReader(this.ResponseStream);
- } else {
- return new StreamReader(this.ResponseStream, Encoding.GetEncoding(contentEncoding));
+ Encoding encoding = null;
+ if (!string.IsNullOrEmpty(contentEncoding)) {
+ try {
+ encoding = Encoding.GetEncoding(contentEncoding);
+ } catch (ArgumentException ex) {
+ Logger.Messaging.ErrorFormat("Encoding.GetEncoding(\"{0}\") threw ArgumentException: {1}", contentEncoding, ex);
+ }
}
+
+ return encoding != null ? new StreamReader(this.ResponseStream, encoding) : new StreamReader(this.ResponseStream);
}
/// <summary>
diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs
index 7198c78..055ce68 100644
--- a/src/DotNetOpenAuth/Messaging/Channel.cs
+++ b/src/DotNetOpenAuth/Messaging/Channel.cs
@@ -695,27 +695,28 @@ namespace DotNetOpenAuth.Messaging {
WebHeaderCollection headers = new WebHeaderCollection();
headers.Add(HttpResponseHeader.ContentType, "text/html");
- StringWriter bodyWriter = new StringWriter(CultureInfo.InvariantCulture);
- 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));
+ using (StringWriter bodyWriter = new StringWriter(CultureInfo.InvariantCulture)) {
+ 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();
+ OutgoingWebResponse response = new OutgoingWebResponse {
+ Status = HttpStatusCode.OK,
+ Headers = headers,
+ Body = bodyWriter.ToString(),
+ OriginalMessage = message
+ };
+
+ return response;
}
- bodyWriter.WriteLine(
- IndirectMessageFormPostFormat,
- HttpUtility.HtmlEncode(message.Recipient.AbsoluteUri),
- hiddenFields);
- bodyWriter.Flush();
- OutgoingWebResponse response = new OutgoingWebResponse {
- Status = HttpStatusCode.OK,
- Headers = headers,
- Body = bodyWriter.ToString(),
- OriginalMessage = message
- };
-
- return response;
}
/// <summary>
diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs
index 0bbac42..6f8c4f9 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:4.0.30104.0
+// Runtime Version:4.0.30319.1
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -394,6 +394,15 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Looks up a localized string similar to An HttpContext.Current.Session object is required..
+ /// </summary>
+ internal static string SessionRequired {
+ get {
+ return ResourceManager.GetString("SessionRequired", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to Message signature was incorrect..
/// </summary>
internal static string SignatureInvalid {
diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx
index 34385d4..bdf4f68 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx
+++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx
@@ -300,4 +300,7 @@
<data name="BinaryDataRequiresMultipart" xml:space="preserve">
<value>Unable to send all message data because some of it requires multi-part POST, but IMessageWithBinaryData.SendAsMultipart was false.</value>
</data>
+ <data name="SessionRequired" xml:space="preserve">
+ <value>An HttpContext.Current.Session object is required.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
index 7367c01..231637a 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
@@ -89,7 +89,7 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Transforms an OutgoingWebResponse to an MVC-friendly ActionResult.
/// </summary>
- /// <param name="response">The response to send to the uesr agent.</param>
+ /// <param name="response">The response to send to the user agent.</param>
/// <returns>The <see cref="ActionResult"/> instance to be returned by the Controller's action method.</returns>
public static ActionResult AsActionResult(this OutgoingWebResponse response) {
Contract.Requires<ArgumentNullException>(response != null);
@@ -181,7 +181,10 @@ namespace DotNetOpenAuth.Messaging {
/// <typeparam name="T">The type of element contained in the sequence.</typeparam>
/// <param name="sequence">The sequence of sequences to flatten.</param>
/// <returns>A sequence of the contained items.</returns>
+ [Obsolete("Use Enumerable.SelectMany instead.")]
public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<T>> sequence) {
+ ErrorUtilities.VerifyArgumentNotNull(sequence, "sequence");
+
foreach (IEnumerable<T> subsequence in sequence) {
foreach (T item in subsequence) {
yield return item;
diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs
index e9613ab..3524f41 100644
--- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs
+++ b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs
@@ -73,10 +73,10 @@ namespace DotNetOpenAuth.Messaging.Reflection {
Contract.Assume(str != null);
return bool.Parse(str);
};
- Func<string, Identifier> safeIdentfier = str => {
+ Func<string, Identifier> safeIdentifier = str => {
Contract.Assume(str != null);
ErrorUtilities.VerifyFormat(str.Length > 0, MessagingStrings.NonEmptyStringExpected);
- return Identifier.Parse(str);
+ return Identifier.Parse(str, true);
};
Func<byte[], string> safeFromByteArray = bytes => {
Contract.Assume(bytes != null);
@@ -94,7 +94,7 @@ namespace DotNetOpenAuth.Messaging.Reflection {
Map<DateTime>(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), str => XmlConvert.ToDateTime(str, XmlDateTimeSerializationMode.Utc));
Map<byte[]>(safeFromByteArray, safeToByteArray);
Map<Realm>(realm => realm.ToString(), safeRealm);
- Map<Identifier>(id => id.ToString(), safeIdentfier);
+ Map<Identifier>(id => id.SerializedString, safeIdentifier);
Map<bool>(value => value.ToString().ToLowerInvariant(), safeBool);
Map<CultureInfo>(c => c.Name, str => new CultureInfo(str));
Map<CultureInfo[]>(cs => string.Join(",", cs.Select(c => c.Name).ToArray()), str => str.Split(',').Select(s => new CultureInfo(s)).ToArray());
diff --git a/src/DotNetOpenAuth/Migrated rules for DotNetOpenAuth.ruleset b/src/DotNetOpenAuth/Migrated rules for DotNetOpenAuth.ruleset
index cee6f53..db238b6 100644
--- a/src/DotNetOpenAuth/Migrated rules for DotNetOpenAuth.ruleset
+++ b/src/DotNetOpenAuth/Migrated rules for DotNetOpenAuth.ruleset
@@ -5,5 +5,6 @@
<Rule Id="CA1054" Action="None" />
<Rule Id="CA1055" Action="None" />
<Rule Id="CA1056" Action="None" />
+ <Rule Id="CA2104" Action="None" />
</Rules>
</RuleSet> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs
index b45da66..cf09036 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs
@@ -258,6 +258,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// <c>true</c> if the signature on the message is valid; otherwise, <c>false</c>.
/// </returns>
protected virtual bool IsSignatureValid(ITamperResistantOAuthMessage message) {
+ Contract.Requires<ArgumentNullException>(message != null);
+
string signature = this.GetSignature(message);
return message.Signature == signature;
}
diff --git a/src/DotNetOpenAuth/OpenId/Association.cs b/src/DotNetOpenAuth/OpenId/Association.cs
index 62e91ec..3c7e89f 100644
--- a/src/DotNetOpenAuth/OpenId/Association.cs
+++ b/src/DotNetOpenAuth/OpenId/Association.cs
@@ -238,24 +238,28 @@ namespace DotNetOpenAuth.OpenId {
/// </returns>
public override int GetHashCode() {
HMACSHA1 hmac = new HMACSHA1(this.SecretKey);
- CryptoStream cs = new CryptoStream(Stream.Null, hmac, CryptoStreamMode.Write);
+ try {
+ CryptoStream cs = new CryptoStream(Stream.Null, hmac, CryptoStreamMode.Write);
- byte[] hbytes = ASCIIEncoding.ASCII.GetBytes(this.Handle);
+ byte[] hbytes = ASCIIEncoding.ASCII.GetBytes(this.Handle);
- cs.Write(hbytes, 0, hbytes.Length);
- cs.Close();
+ cs.Write(hbytes, 0, hbytes.Length);
+ cs.Close();
- byte[] hash = hmac.Hash;
- hmac.Clear();
+ byte[] hash = hmac.Hash;
+ hmac.Clear();
- long val = 0;
- for (int i = 0; i < hash.Length; i++) {
- val = val ^ (long)hash[i];
- }
+ long val = 0;
+ for (int i = 0; i < hash.Length; i++) {
+ val = val ^ (long)hash[i];
+ }
- val = val ^ this.Expires.ToFileTimeUtc();
+ val = val ^ this.Expires.ToFileTimeUtc();
- return (int)val;
+ return (int)val;
+ } finally {
+ ((IDisposable)hmac).Dispose();
+ }
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs
index 76a1c9b..f178647 100644
--- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs
+++ b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs
@@ -61,6 +61,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI {
/// </summary>
public UIRequest() {
this.LanguagePreference = new[] { CultureInfo.CurrentUICulture };
+ this.Mode = UIModes.Popup;
}
/// <summary>
@@ -75,12 +76,11 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI {
public CultureInfo[] LanguagePreference { get; set; }
/// <summary>
- /// Gets the style of UI that the RP is hosting the OP's authentication page in.
+ /// Gets or sets the style of UI that the RP is hosting the OP's authentication page in.
/// </summary>
/// <value>Some value from the <see cref="UIModes"/> class. Defaults to <see cref="UIModes.Popup"/>.</value>
- [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Design is to allow this later to be changable when more than one value exists.")]
[MessagePart("mode", AllowEmpty = false, IsRequired = true)]
- public string Mode { get { return UIModes.Popup; } }
+ public string Mode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the Relying Party has an icon
diff --git a/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs b/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs
index e96f362..ba9852e 100644
--- a/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs
+++ b/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs
@@ -110,29 +110,29 @@ namespace DotNetOpenAuth.OpenId {
var results = new List<IdentifierDiscoveryResult>();
string signingHost;
- var response = GetXrdsResponse(uriIdentifier, requestHandler, out signingHost);
-
- if (response != null) {
- try {
- var document = new XrdsDocument(XmlReader.Create(response.ResponseStream));
- ValidateXmlDSig(document, uriIdentifier, response, signingHost);
- var xrds = GetXrdElements(document, uriIdentifier.Uri.Host);
-
- // Look for claimed identifier template URIs for an additional XRDS document.
- results.AddRange(GetExternalServices(xrds, uriIdentifier, requestHandler));
-
- // If we couldn't find any claimed identifiers, look for OP identifiers.
- // Normally this would be the opposite (OP Identifiers take precedence over
- // claimed identifiers, but for Google Apps, XRDS' always have OP Identifiers
- // mixed in, which the OpenID spec mandate should eclipse Claimed Identifiers,
- // which would break positive assertion checks).
- if (results.Count == 0) {
- results.AddRange(xrds.CreateServiceEndpoints(uriIdentifier, uriIdentifier));
+ using (var response = GetXrdsResponse(uriIdentifier, requestHandler, out signingHost)) {
+ if (response != null) {
+ try {
+ var document = new XrdsDocument(XmlReader.Create(response.ResponseStream));
+ ValidateXmlDSig(document, uriIdentifier, response, signingHost);
+ var xrds = GetXrdElements(document, uriIdentifier.Uri.Host);
+
+ // Look for claimed identifier template URIs for an additional XRDS document.
+ results.AddRange(GetExternalServices(xrds, uriIdentifier, requestHandler));
+
+ // If we couldn't find any claimed identifiers, look for OP identifiers.
+ // Normally this would be the opposite (OP Identifiers take precedence over
+ // claimed identifiers, but for Google Apps, XRDS' always have OP Identifiers
+ // mixed in, which the OpenID spec mandate should eclipse Claimed Identifiers,
+ // which would break positive assertion checks).
+ if (results.Count == 0) {
+ results.AddRange(xrds.CreateServiceEndpoints(uriIdentifier, uriIdentifier));
+ }
+
+ abortDiscoveryChain = true;
+ } catch (XmlException ex) {
+ Logger.Yadis.ErrorFormat("Error while parsing XRDS document at {0} pointed to by host-meta: {1}", response.FinalUri, ex);
}
-
- abortDiscoveryChain = true;
- } catch (XmlException ex) {
- Logger.Yadis.ErrorFormat("Error while parsing XRDS document at {0} pointed to by host-meta: {1}", response.FinalUri, ex);
}
}
@@ -188,10 +188,11 @@ namespace DotNetOpenAuth.OpenId {
Uri externalLocation = new Uri(templateNode.Value.Trim().Replace("{%uri}", Uri.EscapeDataString(identifier.Uri.AbsoluteUri)));
string nextAuthority = nextAuthorityNode != null ? nextAuthorityNode.Value.Trim() : identifier.Uri.Host;
try {
- var externalXrdsResponse = GetXrdsResponse(identifier, requestHandler, externalLocation);
- XrdsDocument externalXrds = new XrdsDocument(XmlReader.Create(externalXrdsResponse.ResponseStream));
- ValidateXmlDSig(externalXrds, identifier, externalXrdsResponse, nextAuthority);
- results.AddRange(GetXrdElements(externalXrds, identifier).CreateServiceEndpoints(identifier, identifier));
+ using (var externalXrdsResponse = GetXrdsResponse(identifier, requestHandler, externalLocation)) {
+ XrdsDocument externalXrds = new XrdsDocument(XmlReader.Create(externalXrdsResponse.ResponseStream));
+ ValidateXmlDSig(externalXrds, identifier, externalXrdsResponse, nextAuthority);
+ results.AddRange(GetXrdElements(externalXrds, identifier).CreateServiceEndpoints(identifier, identifier));
+ }
} catch (ProtocolException ex) {
Logger.Yadis.WarnFormat("HTTP GET error while retrieving described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex);
} catch (XmlException ex) {
@@ -217,9 +218,9 @@ namespace DotNetOpenAuth.OpenId {
Contract.Requires<ArgumentNullException>(response != null);
var signatureNode = document.Node.SelectSingleNode("/xrds:XRDS/ds:Signature", document.XmlNamespaceResolver);
- ErrorUtilities.VerifyProtocol(signatureNode != null, "Missing Signature element.");
+ ErrorUtilities.VerifyProtocol(signatureNode != null, OpenIdStrings.MissingElement, "Signature");
var signedInfoNode = signatureNode.SelectSingleNode("ds:SignedInfo", document.XmlNamespaceResolver);
- ErrorUtilities.VerifyProtocol(signedInfoNode != null, "Missing SignedInfo element.");
+ ErrorUtilities.VerifyProtocol(signedInfoNode != null, OpenIdStrings.MissingElement, "SignedInfo");
ErrorUtilities.VerifyProtocol(
signedInfoNode.SelectSingleNode("ds:CanonicalizationMethod[@Algorithm='http://docs.oasis-open.org/xri/xrd/2009/01#canonicalize-raw-octets']", document.XmlNamespaceResolver) != null,
"Unrecognized or missing canonicalization method.");
@@ -227,16 +228,19 @@ namespace DotNetOpenAuth.OpenId {
signedInfoNode.SelectSingleNode("ds:SignatureMethod[@Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1']", document.XmlNamespaceResolver) != null,
"Unrecognized or missing signature method.");
var certNodes = signatureNode.Select("ds:KeyInfo/ds:X509Data/ds:X509Certificate", document.XmlNamespaceResolver);
- ErrorUtilities.VerifyProtocol(certNodes.Count > 0, "Missing X509Certificate element.");
+ ErrorUtilities.VerifyProtocol(certNodes.Count > 0, OpenIdStrings.MissingElement, "X509Certificate");
var certs = certNodes.Cast<XPathNavigator>().Select(n => new X509Certificate2(Convert.FromBase64String(n.Value.Trim()))).ToList();
// Verify that we trust the signer of the certificates.
// Start by trying to validate just the certificate used to sign the XRDS document,
// since we can do that with partial trust.
+ Logger.OpenId.Debug("Verifying that we trust the certificate used to sign the discovery document.");
if (!certs[0].Verify()) {
// We couldn't verify just the signing certificate, so try to verify the whole certificate chain.
try {
+ Logger.OpenId.Debug("Verifying the whole certificate chain.");
VerifyCertChain(certs);
+ Logger.OpenId.Debug("Certificate chain verified.");
} catch (SecurityException) {
Logger.Yadis.Warn("Signing certificate verification failed and we have insufficient code access security permissions to perform certificate chain validation.");
ErrorUtilities.ThrowProtocol(OpenIdStrings.X509CertificateNotTrusted);
@@ -303,7 +307,7 @@ namespace DotNetOpenAuth.OpenId {
request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy;
request.Accept = ContentTypes.Xrds;
var options = identifier.IsDiscoverySecureEndToEnd ? DirectWebRequestOptions.RequireSsl : DirectWebRequestOptions.None;
- var response = requestHandler.GetResponse(request, options);
+ var response = requestHandler.GetResponse(request, options).GetSnapshot(Yadis.MaximumResultToScan);
if (!string.Equals(response.ContentType.MediaType, ContentTypes.Xrds, StringComparison.Ordinal)) {
Logger.Yadis.WarnFormat("Host-meta pointed to XRDS at {0}, but Content-Type at that URL was unexpected value '{1}'.", xrdsLocation, response.ContentType);
}
@@ -342,23 +346,24 @@ namespace DotNetOpenAuth.OpenId {
private Uri GetXrdsLocation(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) {
Contract.Requires<ArgumentNullException>(identifier != null);
Contract.Requires<ArgumentNullException>(requestHandler != null);
- var hostMetaResponse = this.GetHostMeta(identifier, requestHandler, out signingHost);
- if (hostMetaResponse == null) {
- return null;
- }
+ using (var hostMetaResponse = this.GetHostMeta(identifier, requestHandler, out signingHost)) {
+ if (hostMetaResponse == null) {
+ return null;
+ }
- using (var sr = hostMetaResponse.GetResponseReader()) {
- string line = sr.ReadLine();
- Match m = HostMetaLink.Match(line);
- if (m.Success) {
- Uri location = new Uri(m.Groups["location"].Value);
- Logger.Yadis.InfoFormat("Found link to XRDS at {0} in host-meta document {1}.", location, hostMetaResponse.FinalUri);
- return location;
+ using (var sr = hostMetaResponse.GetResponseReader()) {
+ string line = sr.ReadLine();
+ Match m = HostMetaLink.Match(line);
+ if (m.Success) {
+ Uri location = new Uri(m.Groups["location"].Value);
+ Logger.Yadis.InfoFormat("Found link to XRDS at {0} in host-meta document {1}.", location, hostMetaResponse.FinalUri);
+ return location;
+ }
}
- }
- Logger.Yadis.WarnFormat("Could not find link to XRDS in host-meta document: {0}", hostMetaResponse.FinalUri);
- return null;
+ Logger.Yadis.WarnFormat("Could not find link to XRDS in host-meta document: {0}", hostMetaResponse.FinalUri);
+ return null;
+ }
}
/// <summary>
@@ -381,13 +386,19 @@ namespace DotNetOpenAuth.OpenId {
if (identifier.IsDiscoverySecureEndToEnd) {
options |= DirectWebRequestOptions.RequireSsl;
}
- var response = requestHandler.GetResponse(request, options);
- if (response.Status == HttpStatusCode.OK) {
- Logger.Yadis.InfoFormat("Found host-meta for {0} at: {1}", identifier.Uri.Host, hostMetaLocation);
- signingHost = hostMetaProxy.GetSigningHost(identifier);
- return response;
- } else {
- Logger.Yadis.InfoFormat("Could not obtain host-meta for {0} from {1}", identifier.Uri.Host, hostMetaLocation);
+ var response = requestHandler.GetResponse(request, options).GetSnapshot(Yadis.MaximumResultToScan);
+ try {
+ if (response.Status == HttpStatusCode.OK) {
+ Logger.Yadis.InfoFormat("Found host-meta for {0} at: {1}", identifier.Uri.Host, hostMetaLocation);
+ signingHost = hostMetaProxy.GetSigningHost(identifier);
+ return response;
+ } else {
+ Logger.Yadis.InfoFormat("Could not obtain host-meta for {0} from {1}", identifier.Uri.Host, hostMetaLocation);
+ response.Dispose();
+ }
+ } catch {
+ response.Dispose();
+ throw;
}
}
diff --git a/src/DotNetOpenAuth/OpenId/Identifier.cs b/src/DotNetOpenAuth/OpenId/Identifier.cs
index 2ab5360..36ec784 100644
--- a/src/DotNetOpenAuth/OpenId/Identifier.cs
+++ b/src/DotNetOpenAuth/OpenId/Identifier.cs
@@ -39,6 +39,18 @@ namespace DotNetOpenAuth.OpenId {
public string OriginalString { get; private set; }
/// <summary>
+ /// Gets the Identifier in the form in which it should be serialized.
+ /// </summary>
+ /// <value>
+ /// For Identifiers that were originally deserialized, this is the exact same
+ /// string that was deserialized. For Identifiers instantiated in some other way, this is
+ /// the normalized form of the string used to instantiate the identifier.
+ /// </value>
+ internal virtual string SerializedString {
+ get { return this.IsDeserializedInstance ? this.OriginalString : this.ToString(); }
+ }
+
+ /// <summary>
/// Gets or sets a value indicating whether <see cref="Identifier"/> instances are considered equal
/// based solely on their string reprsentations.
/// </summary>
@@ -59,6 +71,18 @@ namespace DotNetOpenAuth.OpenId {
protected internal bool IsDiscoverySecureEndToEnd { get; private set; }
/// <summary>
+ /// Gets a value indicating whether this instance was initialized from
+ /// deserializing a message.
+ /// </summary>
+ /// <remarks>
+ /// This is interesting because when an Identifier comes from the network,
+ /// we can't normalize it and then expect signatures to still verify.
+ /// But if the Identifier is initialized locally, we can and should normalize it
+ /// before serializing it.
+ /// </remarks>
+ protected bool IsDeserializedInstance { get; private set; }
+
+ /// <summary>
/// Converts the string representation of an Identifier to its strong type.
/// </summary>
/// <param name="identifier">The identifier.</param>
@@ -118,11 +142,32 @@ namespace DotNetOpenAuth.OpenId {
Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(identifier));
Contract.Ensures(Contract.Result<Identifier>() != null);
+ return Parse(identifier, false);
+ }
+
+ /// <summary>
+ /// Parses an identifier string and automatically determines
+ /// whether it is an XRI or URI.
+ /// </summary>
+ /// <param name="identifier">Either a URI or XRI identifier.</param>
+ /// <param name="serializeExactValue">if set to <c>true</c> this Identifier will serialize exactly as given rather than in its normalized form.</param>
+ /// <returns>
+ /// An <see cref="Identifier"/> instance for the given value.
+ /// </returns>
+ [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")]
+ public static Identifier Parse(string identifier, bool serializeExactValue) {
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(identifier));
+ Contract.Ensures(Contract.Result<Identifier>() != null);
+
+ Identifier id;
if (XriIdentifier.IsValidXri(identifier)) {
- return new XriIdentifier(identifier);
+ id = new XriIdentifier(identifier);
} else {
- return new UriIdentifier(identifier);
+ id = new UriIdentifier(identifier);
}
+
+ id.IsDeserializedInstance = serializeExactValue;
+ return id;
}
/// <summary>
@@ -212,6 +257,19 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Reparses the specified identifier in order to be assured that the concrete type that
+ /// implements the identifier is one of the well-known ones.
+ /// </summary>
+ /// <param name="identifier">The identifier.</param>
+ /// <returns>Either <see cref="XriIdentifier"/> or <see cref="UriIdentifier"/>.</returns>
+ internal static Identifier Reparse(Identifier identifier) {
+ Contract.Requires<ArgumentNullException>(identifier != null);
+ Contract.Ensures(Contract.Result<Identifier>() != null);
+
+ return Parse(identifier, identifier.IsDeserializedInstance);
+ }
+
+ /// <summary>
/// Returns an <see cref="Identifier"/> that has no URI fragment.
/// Quietly returns the original <see cref="Identifier"/> if it is not
/// a <see cref="UriIdentifier"/> or no fragment exists.
diff --git a/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs b/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs
index 3190920..c851f24 100644
--- a/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs
+++ b/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs
@@ -89,7 +89,7 @@ namespace DotNetOpenAuth.OpenId {
// not a derived type that will override expected behavior.
// Elsewhere in this class, we count on the fact that this property
// is either UriIdentifier or XriIdentifier. MockIdentifier messes it up.
- this.claimedIdentifier = value != null ? Identifier.Parse(value) : null;
+ this.claimedIdentifier = value != null ? Identifier.Reparse(value) : null;
}
}
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
index 29315bb..43283ac 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:4.0.30104.0
+// Runtime Version:4.0.30319.1
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -196,6 +196,15 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to This OpenID exploits features that this relying party cannot reliably verify. Please try logging in with a human-readable OpenID or from a different OpenID Provider..
+ /// </summary>
+ internal static string ClaimedIdentifierDefiesDotNetNormalization {
+ get {
+ return ResourceManager.GetString("ClaimedIdentifierDefiesDotNetNormalization", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The ClaimedIdentifier property must be set first..
/// </summary>
internal static string ClaimedIdentifierMustBeSetFirst {
@@ -416,6 +425,15 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to Missing {0} element..
+ /// </summary>
+ internal static string MissingElement {
+ get {
+ return ResourceManager.GetString("MissingElement", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to No recognized association type matches the requested length of {0}..
/// </summary>
internal static string NoAssociationTypeFoundByLength {
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
index ae68fe6..fab03a9 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
@@ -349,4 +349,10 @@ Discovered endpoint info:
<data name="X509CertificateNotTrusted" xml:space="preserve">
<value>The X.509 certificate used to sign this document is not trusted.</value>
</data>
+ <data name="ClaimedIdentifierDefiesDotNetNormalization" xml:space="preserve">
+ <value>This OpenID exploits features that this relying party cannot reliably verify. Please try logging in with a human-readable OpenID or from a different OpenID Provider.</value>
+ </data>
+ <data name="MissingElement" xml:space="preserve">
+ <value>Missing {0} element.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs
index 445978e..e792a81 100644
--- a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs
+++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs
@@ -98,11 +98,15 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// </remarks>
public static IAuthenticationRequest PendingAuthenticationRequest {
get {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired);
Contract.Ensures(Contract.Result<IAuthenticationRequest>() == null || PendingRequest != null);
return HttpContext.Current.Session[PendingRequestKey] as IAuthenticationRequest;
}
set {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired);
HttpContext.Current.Session[PendingRequestKey] = value;
}
}
@@ -118,11 +122,15 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// </remarks>
public static IAnonymousRequest PendingAnonymousRequest {
get {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired);
Contract.Ensures(Contract.Result<IAnonymousRequest>() == null || PendingRequest != null);
return HttpContext.Current.Session[PendingRequestKey] as IAnonymousRequest;
}
set {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired);
HttpContext.Current.Session[PendingRequestKey] = value;
}
}
@@ -137,8 +145,17 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// before responding to the relying party's request.
/// </remarks>
public static IHostProcessedRequest PendingRequest {
- get { return HttpContext.Current.Session[PendingRequestKey] as IHostProcessedRequest; }
- set { HttpContext.Current.Session[PendingRequestKey] = value; }
+ get {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired);
+ return HttpContext.Current.Session[PendingRequestKey] as IHostProcessedRequest;
+ }
+
+ set {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired);
+ Contract.Requires<InvalidOperationException>(HttpContext.Current.Session != null, MessagingStrings.SessionRequired);
+ HttpContext.Current.Session[PendingRequestKey] = value;
+ }
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs
index c13c61c..4aa78a5 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs
@@ -702,79 +702,104 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
// top row, left cell
cell = new TableCell();
- this.label = new HtmlGenericControl("label");
- this.label.InnerText = LabelTextDefault;
- cell.Controls.Add(this.label);
- row1.Cells.Add(cell);
+ try {
+ this.label = new HtmlGenericControl("label");
+ this.label.InnerText = LabelTextDefault;
+ cell.Controls.Add(this.label);
+ row1.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// top row, middle cell
cell = new TableCell();
- cell.Controls.Add(new InPlaceControl(this));
- row1.Cells.Add(cell);
+ try {
+ cell.Controls.Add(new InPlaceControl(this));
+ row1.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// top row, right cell
cell = new TableCell();
- this.loginButton = new Button();
- this.loginButton.ID = "loginButton";
- this.loginButton.Text = ButtonTextDefault;
- this.loginButton.ToolTip = ButtonToolTipDefault;
- this.loginButton.Click += this.LoginButton_Click;
- this.loginButton.ValidationGroup = ValidationGroupDefault;
+ try {
+ this.loginButton = new Button();
+ this.loginButton.ID = "loginButton";
+ this.loginButton.Text = ButtonTextDefault;
+ this.loginButton.ToolTip = ButtonToolTipDefault;
+ this.loginButton.Click += this.LoginButton_Click;
+ this.loginButton.ValidationGroup = ValidationGroupDefault;
#if !Mono
- this.panel.DefaultButton = this.loginButton.ID;
+ this.panel.DefaultButton = this.loginButton.ID;
#endif
- cell.Controls.Add(this.loginButton);
- row1.Cells.Add(cell);
+ cell.Controls.Add(this.loginButton);
+ row1.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// middle row, left cell
row2.Cells.Add(new TableCell());
// middle row, middle cell
cell = new TableCell();
- cell.Style[HtmlTextWriterStyle.Color] = "gray";
- cell.Style[HtmlTextWriterStyle.FontSize] = "smaller";
- this.requiredValidator = new RequiredFieldValidator();
- this.requiredValidator.ErrorMessage = RequiredTextDefault + RequiredTextSuffix;
- this.requiredValidator.Text = RequiredTextDefault + RequiredTextSuffix;
- this.requiredValidator.Display = ValidatorDisplay.Dynamic;
- this.requiredValidator.ValidationGroup = ValidationGroupDefault;
- cell.Controls.Add(this.requiredValidator);
- this.identifierFormatValidator = new CustomValidator();
- this.identifierFormatValidator.ErrorMessage = UriFormatTextDefault + RequiredTextSuffix;
- this.identifierFormatValidator.Text = UriFormatTextDefault + RequiredTextSuffix;
- this.identifierFormatValidator.ServerValidate += this.IdentifierFormatValidator_ServerValidate;
- this.identifierFormatValidator.Enabled = UriValidatorEnabledDefault;
- this.identifierFormatValidator.Display = ValidatorDisplay.Dynamic;
- this.identifierFormatValidator.ValidationGroup = ValidationGroupDefault;
- cell.Controls.Add(this.identifierFormatValidator);
- this.errorLabel = new Label();
- this.errorLabel.EnableViewState = false;
- this.errorLabel.ForeColor = System.Drawing.Color.Red;
- this.errorLabel.Style[HtmlTextWriterStyle.Display] = "block"; // puts it on its own line
- this.errorLabel.Visible = false;
- cell.Controls.Add(this.errorLabel);
- this.examplePrefixLabel = new Label();
- this.examplePrefixLabel.Text = ExamplePrefixDefault;
- cell.Controls.Add(this.examplePrefixLabel);
- cell.Controls.Add(new LiteralControl(" "));
- this.exampleUrlLabel = new Label();
- this.exampleUrlLabel.Font.Bold = true;
- this.exampleUrlLabel.Text = ExampleUrlDefault;
- cell.Controls.Add(this.exampleUrlLabel);
- row2.Cells.Add(cell);
+ try {
+ cell.Style[HtmlTextWriterStyle.Color] = "gray";
+ cell.Style[HtmlTextWriterStyle.FontSize] = "smaller";
+ this.requiredValidator = new RequiredFieldValidator();
+ this.requiredValidator.ErrorMessage = RequiredTextDefault + RequiredTextSuffix;
+ this.requiredValidator.Text = RequiredTextDefault + RequiredTextSuffix;
+ this.requiredValidator.Display = ValidatorDisplay.Dynamic;
+ this.requiredValidator.ValidationGroup = ValidationGroupDefault;
+ cell.Controls.Add(this.requiredValidator);
+ this.identifierFormatValidator = new CustomValidator();
+ this.identifierFormatValidator.ErrorMessage = UriFormatTextDefault + RequiredTextSuffix;
+ this.identifierFormatValidator.Text = UriFormatTextDefault + RequiredTextSuffix;
+ this.identifierFormatValidator.ServerValidate += this.IdentifierFormatValidator_ServerValidate;
+ this.identifierFormatValidator.Enabled = UriValidatorEnabledDefault;
+ this.identifierFormatValidator.Display = ValidatorDisplay.Dynamic;
+ this.identifierFormatValidator.ValidationGroup = ValidationGroupDefault;
+ cell.Controls.Add(this.identifierFormatValidator);
+ this.errorLabel = new Label();
+ this.errorLabel.EnableViewState = false;
+ this.errorLabel.ForeColor = System.Drawing.Color.Red;
+ this.errorLabel.Style[HtmlTextWriterStyle.Display] = "block"; // puts it on its own line
+ this.errorLabel.Visible = false;
+ cell.Controls.Add(this.errorLabel);
+ this.examplePrefixLabel = new Label();
+ this.examplePrefixLabel.Text = ExamplePrefixDefault;
+ cell.Controls.Add(this.examplePrefixLabel);
+ cell.Controls.Add(new LiteralControl(" "));
+ this.exampleUrlLabel = new Label();
+ this.exampleUrlLabel.Font.Bold = true;
+ this.exampleUrlLabel.Text = ExampleUrlDefault;
+ cell.Controls.Add(this.exampleUrlLabel);
+ row2.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// middle row, right cell
cell = new TableCell();
- cell.Style[HtmlTextWriterStyle.Color] = "gray";
- cell.Style[HtmlTextWriterStyle.FontSize] = "smaller";
- cell.Style[HtmlTextWriterStyle.TextAlign] = "center";
- this.registerLink = new HyperLink();
- this.registerLink.Text = RegisterTextDefault;
- this.registerLink.ToolTip = RegisterToolTipDefault;
- this.registerLink.NavigateUrl = RegisterUrlDefault;
- this.registerLink.Visible = RegisterVisibleDefault;
- cell.Controls.Add(this.registerLink);
- row2.Cells.Add(cell);
+ try {
+ cell.Style[HtmlTextWriterStyle.Color] = "gray";
+ cell.Style[HtmlTextWriterStyle.FontSize] = "smaller";
+ cell.Style[HtmlTextWriterStyle.TextAlign] = "center";
+ this.registerLink = new HyperLink();
+ this.registerLink.Text = RegisterTextDefault;
+ this.registerLink.ToolTip = RegisterToolTipDefault;
+ this.registerLink.NavigateUrl = RegisterUrlDefault;
+ this.registerLink.Visible = RegisterVisibleDefault;
+ cell.Controls.Add(this.registerLink);
+ row2.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// bottom row, left cell
cell = new TableCell();
@@ -782,17 +807,27 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
// bottom row, middle cell
cell = new TableCell();
- this.rememberMeCheckBox = new CheckBox();
- this.rememberMeCheckBox.Text = RememberMeTextDefault;
- this.rememberMeCheckBox.Checked = this.UsePersistentCookie != LogOnPersistence.Session;
- this.rememberMeCheckBox.Visible = RememberMeVisibleDefault;
- this.rememberMeCheckBox.CheckedChanged += this.RememberMeCheckBox_CheckedChanged;
- cell.Controls.Add(this.rememberMeCheckBox);
- row3.Cells.Add(cell);
+ try {
+ this.rememberMeCheckBox = new CheckBox();
+ this.rememberMeCheckBox.Text = RememberMeTextDefault;
+ this.rememberMeCheckBox.Checked = this.UsePersistentCookie != LogOnPersistence.Session;
+ this.rememberMeCheckBox.Visible = RememberMeVisibleDefault;
+ this.rememberMeCheckBox.CheckedChanged += this.RememberMeCheckBox_CheckedChanged;
+ cell.Controls.Add(this.rememberMeCheckBox);
+ row3.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// bottom row, right cell
cell = new TableCell();
- row3.Cells.Add(cell);
+ try {
+ row3.Cells.Add(cell);
+ } catch {
+ cell.Dispose();
+ throw;
+ }
// this sets all the controls' tab indexes
this.TabIndex = TabIndexDefault;
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs
index dbf9530..8684bd1 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs
@@ -762,13 +762,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
IRelyingPartyApplicationStore store = this.Stateless ? null :
(this.CustomApplicationStore ?? DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore));
var rp = new OpenIdRelyingParty(store);
-
- // Only set RequireSsl to true, as we don't want to override
- // a .config setting of true with false.
- if (this.RequireSsl) {
- rp.SecuritySettings.RequireSsl = true;
+ try {
+ // Only set RequireSsl to true, as we don't want to override
+ // a .config setting of true with false.
+ if (this.RequireSsl) {
+ rp.SecuritySettings.RequireSsl = true;
+ }
+ return rp;
+ } catch {
+ rp.Dispose();
+ throw;
}
- return rp;
}
}
}
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs
index 37ba8c1..f22645f 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs
@@ -321,7 +321,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// <param name="identifiers">The identifiers to perform discovery on.</param>
protected void PreloadDiscovery(IEnumerable<Identifier> identifiers) {
string script = this.AjaxRelyingParty.AsAjaxPreloadedDiscoveryResult(
- identifiers.Select(id => this.CreateRequests(id)).Flatten());
+ identifiers.SelectMany(id => this.CreateRequests(id)));
this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyAjaxControlBase), this.ClientID, script, true);
}
@@ -399,6 +399,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// </summary>
/// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param>
protected override void Render(HtmlTextWriter writer) {
+ Contract.Assume(writer != null, "Missing contract.");
base.Render(writer);
// Emit a hidden field to let the javascript on the user agent know if an
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs
index 838b749..5090ecd 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs
@@ -358,6 +358,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
set {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(value));
+
if (Page != null && !DesignMode) {
// Validate new value by trying to construct a Realm object based on it.
new Realm(OpenIdUtilities.GetResolvedRealm(this.Page, value, this.RelyingParty.Channel.GetRequestFromContext())); // throws an exception on failure.
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs
index 5b85e7c..b7a54eb 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs
@@ -310,11 +310,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
this.EnsureValidButtons();
var css = new HtmlLink();
- css.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedStylesheetResourceName);
- css.Attributes["rel"] = "stylesheet";
- css.Attributes["type"] = "text/css";
- ErrorUtilities.VerifyHost(this.Page.Header != null, OpenIdStrings.HeadTagMustIncludeRunatServer);
- this.Page.Header.Controls.AddAt(0, css); // insert at top so host page can override
+ try {
+ css.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedStylesheetResourceName);
+ css.Attributes["rel"] = "stylesheet";
+ css.Attributes["type"] = "text/css";
+ ErrorUtilities.VerifyHost(this.Page.Header != null, OpenIdStrings.HeadTagMustIncludeRunatServer);
+ this.Page.Header.Controls.AddAt(0, css); // insert at top so host page can override
+ } catch {
+ css.Dispose();
+ throw;
+ }
// Import the .js file where most of the code is.
this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdSelector), EmbeddedScriptResourceName);
@@ -344,6 +349,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// </summary>
/// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param>
protected override void Render(HtmlTextWriter writer) {
+ Contract.Assume(writer != null, "Missing contract");
writer.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIdProviders");
writer.RenderBeginTag(HtmlTextWriterTag.Ul);
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs
index 08e7aac..335b435 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs
@@ -584,6 +584,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// </summary>
/// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param>
protected override void Render(HtmlTextWriter writer) {
+ Contract.Assume(writer != null, "Missing contract.");
+
if (this.ShowLogo) {
string logoUrl = Page.ClientScript.GetWebResourceUrl(
typeof(OpenIdTextBox), EmbeddedLogoResourceName);
@@ -625,6 +627,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// true if the server control's state changes as a result of the postback; otherwise, false.
/// </returns>
protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) {
+ Contract.Assume(postCollection != null, "Missing contract");
+
// If the control was temporarily hidden, it won't be in the Form data,
// and we'll just implicitly keep the last Text setting.
if (postCollection[this.Name] != null) {
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs
index b6a1b76..3e2298c 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs
@@ -146,6 +146,14 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
}
+ // Check whether this particular identifier presents a problem with HTTP discovery
+ // due to limitations in the .NET Uri class.
+ UriIdentifier claimedIdUri = claimedId as UriIdentifier;
+ if (claimedIdUri != null && claimedIdUri.ProblematicNormalization) {
+ ErrorUtilities.VerifyProtocol(relyingParty.SecuritySettings.AllowApproximateIdentifierDiscovery, OpenIdStrings.ClaimedIdentifierDefiesDotNetNormalization);
+ Logger.OpenId.WarnFormat("Positive assertion for claimed identifier {0} cannot be precisely verified under partial trust hosting due to .NET limitation. An approximate verification will be attempted.", claimedId);
+ }
+
// While it LOOKS like we're performing discovery over HTTP again
// Yadis.IdentifierDiscoveryCachePolicy is set to HttpRequestCacheLevel.CacheIfAvailable
// which means that the .NET runtime is caching our discoveries for us. This turns out
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
index e2bf2a1..a7686c5 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
@@ -27,6 +27,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
: base(false) {
this.PrivateSecretMaximumAge = TimeSpan.FromDays(7);
this.ProtectDownlevelReplayAttacks = ProtectDownlevelReplayAttacksDefault;
+ this.AllowApproximateIdentifierDiscovery = true;
}
/// <summary>
@@ -132,6 +133,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
public bool AllowDualPurposeIdentifiers { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether certain Claimed Identifiers that exploit
+ /// features that .NET does not have the ability to send exact HTTP requests for will
+ /// still be allowed by using an approximate HTTP request.
+ /// </summary>
+ /// <value>
+ /// The default value is <c>true</c>.
+ /// </value>
+ public bool AllowApproximateIdentifierDiscovery { get; set; }
+
+ /// <summary>
/// Gets or sets a value indicating whether special measures are taken to
/// protect users from replay attacks when those users' identities are hosted
/// by OpenID 1.x Providers.
diff --git a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs
index 0b7a0e3..278ae37 100644
--- a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs
+++ b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs
@@ -10,6 +10,9 @@ namespace DotNetOpenAuth.OpenId {
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
+ using System.Reflection;
+ using System.Security;
+ using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI.HtmlControls;
using System.Xml;
@@ -30,6 +33,68 @@ namespace DotNetOpenAuth.OpenId {
private static readonly string[] allowedSchemes = { "http", "https" };
/// <summary>
+ /// The special scheme to use for HTTP URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser roundTrippingHttpParser = new NonPathCompressingUriParser(Uri.UriSchemeHttp);
+
+ /// <summary>
+ /// The special scheme to use for HTTPS URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser roundTrippingHttpsParser = new NonPathCompressingUriParser(Uri.UriSchemeHttps);
+
+ /// <summary>
+ /// The special scheme to use for HTTP URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser publishableHttpParser = new NonPathCompressingUriParser(Uri.UriSchemeHttp);
+
+ /// <summary>
+ /// The special scheme to use for HTTPS URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser publishableHttpsParser = new NonPathCompressingUriParser(Uri.UriSchemeHttps);
+
+ /// <summary>
+ /// A value indicating whether scheme substitution is being used to workaround
+ /// .NET path compression that invalidates some OpenIDs that have trailing periods
+ /// in one of their path segments.
+ /// </summary>
+ private static bool schemeSubstitution;
+
+ /// <summary>
+ /// Initializes static members of the <see cref="UriIdentifier"/> class.
+ /// </summary>
+ /// <remarks>
+ /// This method attempts to workaround the .NET Uri class parsing bug described here:
+ /// https://connect.microsoft.com/VisualStudio/feedback/details/386695/system-uri-incorrectly-strips-trailing-dots?wa=wsignin1.0#tabs
+ /// since some identifiers (like some of the pseudonymous identifiers from Yahoo) include path segments
+ /// that end with periods, which the Uri class will typically trim off.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Some things just can't be done in a field initializer.")]
+ static UriIdentifier() {
+ // Our first attempt to handle trailing periods in path segments is to leverage
+ // full trust if it's available to rewrite the rules.
+ // In fact this is the ONLY way in .NET 3.5 (and arguably in .NET 4.0) to send
+ // outbound HTTP requests with trailing periods, so it's the only way to perform
+ // discovery on such an identifier.
+ try {
+ UriParser.Register(roundTrippingHttpParser, "dnoarthttp", 80);
+ UriParser.Register(roundTrippingHttpsParser, "dnoarthttps", 443);
+ UriParser.Register(publishableHttpParser, "dnoahttp", 80);
+ UriParser.Register(publishableHttpsParser, "dnoahttps", 443);
+ roundTrippingHttpParser.Initialize(false);
+ roundTrippingHttpsParser.Initialize(false);
+ publishableHttpParser.Initialize(true);
+ publishableHttpsParser.Initialize(true);
+ schemeSubstitution = true;
+ Logger.OpenId.Debug(".NET Uri class path compression overridden.");
+ Reporting.RecordFeatureUse("FullTrust");
+ } catch (SecurityException) {
+ // We must be running in partial trust. Nothing more we can do.
+ Logger.OpenId.Warn("Unable to coerce .NET to stop compressing URI paths due to partial trust limitations. Some URL identifiers may be unable to complete login.");
+ Reporting.RecordFeatureUse("PartialTrust");
+ }
+ }
+
+ /// <summary>
/// Initializes a new instance of the <see cref="UriIdentifier"/> class.
/// </summary>
/// <param name="uri">The value this identifier will represent.</param>
@@ -62,7 +127,8 @@ namespace DotNetOpenAuth.OpenId {
/// Initializes a new instance of the <see cref="UriIdentifier"/> class.
/// </summary>
/// <param name="uri">The value this identifier will represent.</param>
- internal UriIdentifier(Uri uri) : this(uri, false) {
+ internal UriIdentifier(Uri uri)
+ : this(uri, false) {
}
/// <summary>
@@ -73,7 +139,13 @@ namespace DotNetOpenAuth.OpenId {
internal UriIdentifier(Uri uri, bool requireSslDiscovery)
: base(uri != null ? uri.OriginalString : null, requireSslDiscovery) {
Contract.Requires<ArgumentNullException>(uri != null);
- if (!TryCanonicalize(new UriBuilder(uri), out uri)) {
+
+ string uriAsString = uri.OriginalString;
+ if (schemeSubstitution) {
+ uriAsString = NormalSchemeToSpecialRoundTrippingScheme(uriAsString);
+ }
+
+ if (!TryCanonicalize(uriAsString, out uri)) {
throw new UriFormatException();
}
if (requireSslDiscovery && uri.Scheme != Uri.UriSchemeHttps) {
@@ -96,6 +168,26 @@ namespace DotNetOpenAuth.OpenId {
internal bool SchemeImplicitlyPrepended { get; private set; }
/// <summary>
+ /// Gets a value indicating whether this Identifier has characters or patterns that
+ /// the <see cref="Uri"/> class normalizes away and invalidating the Identifier.
+ /// </summary>
+ internal bool ProblematicNormalization {
+ get {
+ if (schemeSubstitution) {
+ // With full trust, we have no problematic URIs
+ return false;
+ }
+
+ var simpleUri = new SimpleUri(this.OriginalString);
+ if (simpleUri.Path.EndsWith(".", StringComparison.Ordinal) || simpleUri.Path.Contains("./")) {
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ /// <summary>
/// Converts a <see cref="UriIdentifier"/> instance to a <see cref="Uri"/> instance.
/// </summary>
/// <param name="identifier">The identifier to convert to an ordinary <see cref="Uri"/> instance.</param>
@@ -137,7 +229,12 @@ namespace DotNetOpenAuth.OpenId {
if (other == null) {
return false;
}
- return this.Uri == other.Uri;
+
+ if (this.ProblematicNormalization || other.ProblematicNormalization) {
+ return new SimpleUri(this.OriginalString).Equals(new SimpleUri(other.OriginalString));
+ } else {
+ return this.Uri == other.Uri;
+ }
}
/// <summary>
@@ -157,16 +254,13 @@ namespace DotNetOpenAuth.OpenId {
/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
/// </returns>
public override string ToString() {
- return Uri.AbsoluteUri;
- }
-#if UNUSED
- static bool TryCanonicalize(string uri, out string canonicalUri) {
- Uri normalizedUri;
- bool result = TryCanonicalize(uri, out normalizedUri);
- canonicalUri = normalizedUri.AbsoluteUri;
- return result;
+ if (this.ProblematicNormalization) {
+ return new SimpleUri(this.OriginalString).ToString();
+ } else {
+ return this.Uri.AbsoluteUri;
+ }
}
-#endif
+
/// <summary>
/// Determines whether a URI is a valid OpenID Identifier (of any kind).
/// </summary>
@@ -222,9 +316,7 @@ namespace DotNetOpenAuth.OpenId {
}
// Strip the fragment.
- UriBuilder builder = new UriBuilder(Uri);
- builder.Fragment = null;
- return builder.Uri;
+ return new UriIdentifier(this.OriginalString.Substring(0, this.OriginalString.IndexOf('#')));
}
/// <summary>
@@ -335,8 +427,12 @@ namespace DotNetOpenAuth.OpenId {
schemePrepended = true;
}
+ if (schemeSubstitution) {
+ uri = NormalSchemeToSpecialRoundTrippingScheme(uri);
+ }
+
// Use a UriBuilder because it helps to normalize the URL as well.
- return TryCanonicalize(new UriBuilder(uri), out canonicalUri);
+ return TryCanonicalize(uri, out canonicalUri);
} catch (UriFormatException) {
// We try not to land here with checks in the try block, but just in case.
return false;
@@ -344,9 +440,9 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
- /// Removes the fragment from a URL and sets the host to lowercase.
+ /// Fixes up the scheme if appropriate.
/// </summary>
- /// <param name="uriBuilder">The URI builder with the value to canonicalize.</param>
+ /// <param name="uri">The URI to canonicalize.</param>
/// <param name="canonicalUri">The resulting canonical URI.</param>
/// <returns><c>true</c> if the canonicalization was successful; <c>false</c> otherwise.</returns>
/// <remarks>
@@ -356,12 +452,48 @@ namespace DotNetOpenAuth.OpenId {
/// For this, you should lookup the value stored in IAuthenticationResponse.ClaimedIdentifier.
/// </remarks>
[SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "The user will see the result of this operation and they want to see it in lower case.")]
- private static bool TryCanonicalize(UriBuilder uriBuilder, out Uri canonicalUri) {
- uriBuilder.Host = uriBuilder.Host.ToLowerInvariant();
- canonicalUri = uriBuilder.Uri;
+ private static bool TryCanonicalize(string uri, out Uri canonicalUri) {
+ Contract.Requires<ArgumentNullException>(uri != null);
+
+ if (schemeSubstitution) {
+ UriBuilder uriBuilder = new UriBuilder(uri);
+
+ // Swap out our round-trippable scheme for the publishable (hidden) scheme.
+ uriBuilder.Scheme = uriBuilder.Scheme == roundTrippingHttpParser.RegisteredScheme ? publishableHttpParser.RegisteredScheme : publishableHttpsParser.RegisteredScheme;
+ canonicalUri = uriBuilder.Uri;
+ } else {
+ canonicalUri = new Uri(uri);
+ }
+
return true;
}
+ /// <summary>
+ /// Gets the special non-compressing scheme or URL for a standard scheme or URL.
+ /// </summary>
+ /// <param name="normal">The ordinary URL or scheme name.</param>
+ /// <returns>The non-compressing equivalent scheme or URL for the given value.</returns>
+ private static string NormalSchemeToSpecialRoundTrippingScheme(string normal) {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(normal));
+ Contract.Requires<InternalErrorException>(schemeSubstitution);
+ Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
+
+ int delimiterIndex = normal.IndexOf(Uri.SchemeDelimiter);
+ string normalScheme = delimiterIndex < 0 ? normal : normal.Substring(0, delimiterIndex);
+ string nonCompressingScheme;
+ if (string.Equals(normalScheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(normalScheme, publishableHttpParser.RegisteredScheme, StringComparison.OrdinalIgnoreCase)) {
+ nonCompressingScheme = roundTrippingHttpParser.RegisteredScheme;
+ } else if (string.Equals(normalScheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(normalScheme, publishableHttpsParser.RegisteredScheme, StringComparison.OrdinalIgnoreCase)) {
+ nonCompressingScheme = roundTrippingHttpsParser.RegisteredScheme;
+ } else {
+ throw new NotSupportedException();
+ }
+
+ return delimiterIndex < 0 ? nonCompressingScheme : nonCompressingScheme + normal.Substring(delimiterIndex);
+ }
+
#if CONTRACTS_FULL
/// <summary>
/// Verifies conditions that should be true for any valid state of this object.
@@ -374,5 +506,178 @@ namespace DotNetOpenAuth.OpenId {
Contract.Invariant(this.Uri.AbsoluteUri != null);
}
#endif
+
+ /// <summary>
+ /// A simple URI class that doesn't suffer from the parsing problems of the <see cref="Uri"/> class.
+ /// </summary>
+ internal class SimpleUri {
+ /// <summary>
+ /// URI characters that separate the URI Path from subsequent elements.
+ /// </summary>
+ private static readonly char[] PathEndingCharacters = new char[] { '?', '#' };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SimpleUri"/> class.
+ /// </summary>
+ /// <param name="value">The value.</param>
+ internal SimpleUri(string value) {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(value));
+
+ // Leverage the Uri class's parsing where we can.
+ Uri uri = new Uri(value);
+ this.Scheme = uri.Scheme;
+ this.Authority = uri.Authority;
+ this.Query = uri.Query;
+ this.Fragment = uri.Fragment;
+
+ // Get the Path out ourselves, since the default Uri parser compresses it too much for OpenID.
+ int schemeLength = value.IndexOf(Uri.SchemeDelimiter, StringComparison.Ordinal);
+ Contract.Assume(schemeLength > 0);
+ int hostStart = schemeLength + Uri.SchemeDelimiter.Length;
+ int hostFinish = value.IndexOf('/', hostStart);
+ if (hostFinish < 0) {
+ this.Path = "/";
+ } else {
+ int pathFinish = value.IndexOfAny(PathEndingCharacters, hostFinish);
+ Contract.Assume(pathFinish >= hostFinish || pathFinish < 0);
+ if (pathFinish < 0) {
+ this.Path = value.Substring(hostFinish);
+ } else {
+ this.Path = value.Substring(hostFinish, pathFinish - hostFinish);
+ }
+ }
+
+ this.Path = NormalizePathEscaping(this.Path);
+ }
+
+ /// <summary>
+ /// Gets the scheme.
+ /// </summary>
+ /// <value>The scheme.</value>
+ public string Scheme { get; private set; }
+
+ /// <summary>
+ /// Gets the authority.
+ /// </summary>
+ /// <value>The authority.</value>
+ public string Authority { get; private set; }
+
+ /// <summary>
+ /// Gets the path of the URI.
+ /// </summary>
+ /// <value>The path from the URI.</value>
+ public string Path { get; private set; }
+
+ /// <summary>
+ /// Gets the query.
+ /// </summary>
+ /// <value>The query.</value>
+ public string Query { get; private set; }
+
+ /// <summary>
+ /// Gets the fragment.
+ /// </summary>
+ /// <value>The fragment.</value>
+ public string Fragment { get; private set; }
+
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents this instance.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="System.String"/> that represents this instance.
+ /// </returns>
+ public override string ToString() {
+ return this.Scheme + Uri.SchemeDelimiter + this.Authority + this.Path + this.Query + this.Fragment;
+ }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
+ /// </summary>
+ /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
+ /// <returns>
+ /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ SimpleUri other = obj as SimpleUri;
+ if (other == null) {
+ return false;
+ }
+
+ // Note that this equality check is intentionally leaving off the Fragment part
+ // to match Uri behavior, and is intentionally being case sensitive and insensitive
+ // for different parts.
+ return string.Equals(this.Scheme, other.Scheme, StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(this.Authority, other.Authority, StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(this.Path, other.Path, StringComparison.Ordinal) &&
+ string.Equals(this.Query, other.Query, StringComparison.Ordinal);
+ }
+
+ /// <summary>
+ /// Normalizes the characters that are escaped in the given URI path.
+ /// </summary>
+ /// <param name="path">The path to normalize.</param>
+ /// <returns>The given path, with exactly those characters escaped which should be.</returns>
+ private static string NormalizePathEscaping(string path) {
+ Contract.Requires<ArgumentNullException>(path != null);
+
+ string[] segments = path.Split('/');
+ for (int i = 0; i < segments.Length; i++) {
+ segments[i] = Uri.EscapeDataString(Uri.UnescapeDataString(segments[i]));
+ }
+
+ return string.Join("/", segments);
+ }
+ }
+
+ /// <summary>
+ /// A URI parser that does not compress paths, such as trimming trailing periods from path segments.
+ /// </summary>
+ private class NonPathCompressingUriParser : GenericUriParser {
+ /// <summary>
+ /// The field that stores the scheme that this parser is registered under.
+ /// </summary>
+ private static FieldInfo schemeField;
+
+ /// <summary>
+ /// The standard "http" or "https" scheme that this parser is subverting.
+ /// </summary>
+ private string standardScheme;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NonPathCompressingUriParser"/> class.
+ /// </summary>
+ /// <param name="standardScheme">The standard scheme that this parser will be subverting.</param>
+ public NonPathCompressingUriParser(string standardScheme)
+ : base(GenericUriParserOptions.DontCompressPath | GenericUriParserOptions.IriParsing | GenericUriParserOptions.Idn) {
+ Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(standardScheme));
+ this.standardScheme = standardScheme;
+ }
+
+ /// <summary>
+ /// Gets the scheme this parser is registered under.
+ /// </summary>
+ /// <value>The registered scheme.</value>
+ internal string RegisteredScheme { get; private set; }
+
+ /// <summary>
+ /// Initializes this parser with the actual scheme it should appear to be.
+ /// </summary>
+ /// <param name="hideNonStandardScheme">if set to <c>true</c> Uris using this scheme will look like they're using the original standard scheme.</param>
+ [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Schemes are traditionally displayed in lowercase.")]
+ internal void Initialize(bool hideNonStandardScheme) {
+ if (schemeField == null) {
+ schemeField = typeof(UriParser).GetField("m_Scheme", BindingFlags.NonPublic | BindingFlags.Instance);
+ }
+
+ this.RegisteredScheme = (string)schemeField.GetValue(this);
+
+ if (hideNonStandardScheme) {
+ schemeField.SetValue(this, this.standardScheme.ToLowerInvariant());
+ }
+ }
+ }
}
}
diff --git a/src/DotNetOpenAuth/Reporting.cs b/src/DotNetOpenAuth/Reporting.cs
index c4421c4..612845f 100644
--- a/src/DotNetOpenAuth/Reporting.cs
+++ b/src/DotNetOpenAuth/Reporting.cs
@@ -151,6 +151,7 @@ namespace DotNetOpenAuth {
/// </summary>
/// <param name="eventName">Name of the event.</param>
/// <param name="category">The category within the event. Null and empty strings are allowed, but considered the same.</param>
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "PersistentCounter instances are stored in a table for later use.")]
internal static void RecordEventOccurrence(string eventName, string category) {
Contract.Requires(!String.IsNullOrEmpty(eventName));
@@ -318,6 +319,7 @@ namespace DotNetOpenAuth {
/// <summary>
/// Initializes Reporting if it has not been initialized yet.
/// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method must never throw.")]
private static void Initialize() {
lock (initializationSync) {
if (!broken && !initialized) {
@@ -355,44 +357,49 @@ namespace DotNetOpenAuth {
/// <returns>A stream that contains the report.</returns>
private static Stream GetReport() {
var stream = new MemoryStream();
- var writer = new StreamWriter(stream, Encoding.UTF8);
- writer.WriteLine(reportOriginIdentity.ToString("B"));
- writer.WriteLine(Util.LibraryVersion);
- writer.WriteLine(".NET Framework {0}", Environment.Version);
-
- foreach (var observation in observations) {
- observation.Flush();
- writer.WriteLine("====================================");
- writer.WriteLine(observation.FileName);
- try {
- using (var fileStream = new IsolatedStorageFileStream(observation.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) {
- writer.Flush();
- fileStream.CopyTo(writer.BaseStream);
+ try {
+ var writer = new StreamWriter(stream, Encoding.UTF8);
+ writer.WriteLine(reportOriginIdentity.ToString("B"));
+ writer.WriteLine(Util.LibraryVersion);
+ writer.WriteLine(".NET Framework {0}", Environment.Version);
+
+ foreach (var observation in observations) {
+ observation.Flush();
+ writer.WriteLine("====================================");
+ writer.WriteLine(observation.FileName);
+ try {
+ using (var fileStream = new IsolatedStorageFileStream(observation.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) {
+ writer.Flush();
+ fileStream.CopyTo(writer.BaseStream);
+ }
+ } catch (FileNotFoundException) {
+ writer.WriteLine("(missing)");
}
- } catch (FileNotFoundException) {
- writer.WriteLine("(missing)");
}
- }
- // Not all event counters may have even loaded in this app instance.
- // We flush the ones in memory, and then read all of them off disk.
- foreach (var counter in events.Values) {
- counter.Flush();
- }
+ // Not all event counters may have even loaded in this app instance.
+ // We flush the ones in memory, and then read all of them off disk.
+ foreach (var counter in events.Values) {
+ counter.Flush();
+ }
- foreach (string eventFile in file.GetFileNames("event-*.txt")) {
- writer.WriteLine("====================================");
- writer.WriteLine(eventFile);
- using (var fileStream = new IsolatedStorageFileStream(eventFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) {
- writer.Flush();
- fileStream.CopyTo(writer.BaseStream);
+ foreach (string eventFile in file.GetFileNames("event-*.txt")) {
+ writer.WriteLine("====================================");
+ writer.WriteLine(eventFile);
+ using (var fileStream = new IsolatedStorageFileStream(eventFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) {
+ writer.Flush();
+ fileStream.CopyTo(writer.BaseStream);
+ }
}
- }
- // Make sure the stream is positioned at the beginning.
- writer.Flush();
- stream.Position = 0;
- return stream;
+ // Make sure the stream is positioned at the beginning.
+ writer.Flush();
+ stream.Position = 0;
+ return stream;
+ } catch {
+ stream.Dispose();
+ throw;
+ }
}
/// <summary>
diff --git a/src/DotNetOpenAuth/Util.cs b/src/DotNetOpenAuth/Util.cs
index 9f8b30c..8a18ef8 100644
--- a/src/DotNetOpenAuth/Util.cs
+++ b/src/DotNetOpenAuth/Util.cs
@@ -126,7 +126,7 @@ namespace DotNetOpenAuth {
sb.Append("\t");
sb.Append(objString);
- if (!objString.EndsWith(Environment.NewLine)) {
+ if (!objString.EndsWith(Environment.NewLine, StringComparison.Ordinal)) {
sb.AppendLine();
}
sb.AppendLine("}, {");
diff --git a/src/DotNetOpenAuth/XrdsPublisher.cs b/src/DotNetOpenAuth/XrdsPublisher.cs
index e7c04d8..83d82ff 100644
--- a/src/DotNetOpenAuth/XrdsPublisher.cs
+++ b/src/DotNetOpenAuth/XrdsPublisher.cs
@@ -9,6 +9,7 @@ namespace DotNetOpenAuth {
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
using System.Drawing.Design;
using System.Text;
using System.Web;
@@ -209,6 +210,7 @@ namespace DotNetOpenAuth {
/// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param>
[SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")]
protected override void Render(HtmlTextWriter writer) {
+ Contract.Assume(writer != null, "Missing contract.");
if (this.Enabled && this.Visible && !string.IsNullOrEmpty(this.XrdsUrl)) {
Uri xrdsAddress = new Uri(MessagingUtilities.GetRequestUrlFromContext(), Page.Response.ApplyAppPathModifier(this.XrdsUrl));
if ((this.XrdsAdvertisement & XrdsUrlLocations.HttpHeader) != 0) {
diff --git a/src/DotNetOpenAuth/Yadis/Yadis.cs b/src/DotNetOpenAuth/Yadis/Yadis.cs
index 7882797..8b8c20f 100644
--- a/src/DotNetOpenAuth/Yadis/Yadis.cs
+++ b/src/DotNetOpenAuth/Yadis/Yadis.cs
@@ -39,7 +39,7 @@ namespace DotNetOpenAuth.Yadis {
/// The maximum number of bytes to read from an HTTP response
/// in searching for a link to a YADIS document.
/// </summary>
- private const int MaximumResultToScan = 1024 * 1024;
+ internal const int MaximumResultToScan = 1024 * 1024;
/// <summary>
/// Performs YADIS discovery on some identifier.
diff --git a/tools/DotNetOpenAuth.automated.props b/tools/DotNetOpenAuth.automated.props
index f66c1fe..6037967 100644
--- a/tools/DotNetOpenAuth.automated.props
+++ b/tools/DotNetOpenAuth.automated.props
@@ -6,6 +6,7 @@
<AutomatedBuild>true</AutomatedBuild>
<SolutionPath>$(ProjectRoot)src\$(ProductName).sln</SolutionPath>
<BuildInParallel Condition=" '$(BuildInParallel)' == '' ">true</BuildInParallel>
+ <DowngradeMvc2ToMvc1 Condition=" '$(DowngradeMvc2ToMvc1)' == '' ">false</DowngradeMvc2ToMvc1>
<!-- Validation controls whether extra builds are done in order to fully validate what we're distributing,
even if we're not distributing the built bits (as is the case for project templates). -->
<Validation Condition=" '$(Validation)' == '' ">Full</Validation>
diff --git a/tools/DotNetOpenAuth.targets b/tools/DotNetOpenAuth.targets
index cab4413..8469aa6 100644
--- a/tools/DotNetOpenAuth.targets
+++ b/tools/DotNetOpenAuth.targets
@@ -17,6 +17,7 @@
<PropertyGroup>
<DefineConstants Condition=" '$(SignAssembly)' == 'true' ">$(DefineConstants);StrongNameSigned</DefineConstants>
<DefineConstants Condition=" '$(ClrVersion)' == '4' ">$(DefineConstants);CLR4</DefineConstants>
+ <AssemblySearchPaths>$(AssemblySearchPaths);$(ProjectRoot)lib</AssemblySearchPaths>
</PropertyGroup>
<ItemGroup>
diff --git a/tools/drop.proj b/tools/drop.proj
index be5d3f9..317aba6 100644
--- a/tools/drop.proj
+++ b/tools/drop.proj
@@ -44,7 +44,7 @@
$(ILMergeOutputAssemblyDirectory)$(SignedSubPath)$(ProductName).dll;
$(ILMergeOutputAssemblyDirectory)$(ProductName).pdb;
$(OutputPath)$(ProductName).xml;
- $(OutputPath)$(SignedSubPath)$(ProductName).Contracts.???;
+ $(OutputPath)CodeContracts\$(SignedSubPath)$(ProductName).Contracts.???;
$(ProjectRoot)Doc\README.Bin.html;
$(ProjectRoot)src\$(ProductName)\Configuration\$(ProductName).xsd;
" />
@@ -133,7 +133,10 @@
AddReferences="Microsoft.Contracts"/>
<ChangeProjectReferenceToAssemblyReference Projects="@(SampleProjectTargets)"
ProjectReferences="..\..\src\$(ProductName)\$(ProductName).csproj" References="..\..\Bin\$(ProductName).dll" />
- <DowngradeProjects Projects="@(SampleProjectTargets);@(SampleSolutionTargets)" DowngradeMvc2ToMvc1="true" />
+ <DowngradeProjects
+ Projects="@(SampleProjectTargets);@(SampleSolutionTargets)"
+ DowngradeMvc2ToMvc1="$(DowngradeMvc2ToMvc1)"
+ />
</Target>
<Target Name="Build" DependsOnTargets="Layout">
diff --git a/vsix/extension.vsixmanifest b/vsix/extension.vsixmanifest
index 20c2472..394574c 100644
--- a/vsix/extension.vsixmanifest
+++ b/vsix/extension.vsixmanifest
@@ -13,12 +13,12 @@
<InstalledByMsi>false</InstalledByMsi>
<SupportedProducts>
<VisualStudio Version="10.0">
- <Edition>VSTS</Edition>
- <Edition>VSTD</Edition>
<Edition>Pro</Edition>
+ <Edition>Premium</Edition>
+ <Edition>Ultimate</Edition>
</VisualStudio>
</SupportedProducts>
- <SupportedFrameworkRuntimeEdition MinVersion="3.5" MaxVersion="3.5" />
+ <SupportedFrameworkRuntimeEdition MinVersion="3.5" MaxVersion="4.0" />
</Identifier>
<References />
<Content>