diff options
119 files changed, 4419 insertions, 214 deletions
@@ -64,6 +64,47 @@ <MSBuild Projects="$(SolutionPath)" Targets="Rebuild" Properties="Sign=$(Sign)" /> </Target> + <Target Name="_EnsureCleanTools" DependsOnTargets="_SetToolsProperties" Condition="'$(NoClean)' != 'true'"> + <!-- clean up any previous drop with the same name so we don't aggregate files. --> + <RemoveDir Directories="$(ToolsDirectory)" /> + </Target> + + <Target Name="_SetToolsProperties"> + <PropertyGroup> + <ToolsDirectory>$(ProjectRoot)\drops\$(ProductName)-Tools-$(BuildVersion)</ToolsDirectory> + </PropertyGroup> + </Target> + + <Target Name="ToolsLayout" DependsOnTargets="GetBuildVersion;_SetDropProperties;_SetToolsProperties;_EnsureCleanTools"> + <ItemGroup> + <ToolProjects Include="$(ProjectRoot)\Samples\OpenIdOfflineProvider\OpenIdOfflineProvider.csproj" /> + <OfflineProvider Include=" + $(ProjectRoot)\Samples\OpenIdOfflineProvider\bin\$(Configuration)\**\*.dll; + $(ProjectRoot)\Samples\OpenIdOfflineProvider\bin\$(Configuration)\OpenIdOfflineProvider.exe" /> + <OfflineProviderTargets Include=" + @(OfflineProvider->'$(ToolsDirectory)\%(RecursiveDir)%(FileName)%(Extension)')"/> + + <AllToolSources Include="@(OfflineProvider)" /> + <AllToolTargets Include="@(OfflineProviderTargets)" /> + </ItemGroup> + + <MSBuild Projects="@(ToolProjects)" Properties="Sign=$(Sign)" /> + + <MakeDir Directories="@(ToolsDirectory)" /> + <Copy SourceFiles="@(AllToolSources)" DestinationFiles="@(AllToolTargets)" SkipUnchangedFiles="true" /> + </Target> + + <Target Name="Tools" DependsOnTargets="ToolsLayout"> + <PropertyGroup> + <ToolsZip>$(ToolsDirectory).zip</ToolsZip> + </PropertyGroup> + + <Delete Files="$(ToolsZip)" /> + <Zip ZipFileName="$(ToolsZip)" + Files="@(AllToolTargets)" + WorkingDirectory="$(ToolsDirectory)" /> + </Target> + <Target Name="Documentation" DependsOnTargets="BuildProduct;Chm" Condition="'$(NoDocumentation)' != 'true'"> </Target> @@ -199,7 +240,7 @@ <!-- Although Nightly includes publishing samples and docs, those targets are conditioned for running only when the SampleWebRoot and DocWebRoot properties are set, respectively. --> - <Target Name="Nightly" DependsOnTargets="Drop;PublishSamples;PublishDocumentation"> + <Target Name="Nightly" DependsOnTargets="Drop;Tools;PublishSamples;PublishDocumentation"> </Target> diff --git a/doc/specs/OpenID OAuth Extension.htm b/doc/specs/OpenID OAuth Extension.htm new file mode 100644 index 0000000..f7e7120 --- /dev/null +++ b/doc/specs/OpenID OAuth Extension.htm @@ -0,0 +1,755 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3c.org/TR/1999/REC-html401-19991224/loose.dtd"> +<!-- saved from url=(0094)http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html --> +<HTML lang=en><HEAD><TITLE>draft: OpenID OAuth Extension</TITLE> +<META content="Wed, 07 Jan 2009 19:57:09 +0000" http-equiv=Expires> +<META content="text/html; charset=utf-8" http-equiv=Content-Type> +<META name=description content="OpenID OAuth Extension"> +<META name=GENERATOR content="MSHTML 8.00.7077.0"> +<STYLE type=text/css>BODY { + BACKGROUND-COLOR: #fff; MARGIN: 2em; FONT-FAMILY: verdana, charcoal, helvetica, arial, sans-serif; COLOR: #000; FONT-SIZE: small +} +H1 { + FONT-STYLE: normal; FONT-FAMILY: helvetica, monaco, "MS Sans Serif", arial, sans-serif; FONT-WEIGHT: bold +} +H2 { + FONT-STYLE: normal; FONT-FAMILY: helvetica, monaco, "MS Sans Serif", arial, sans-serif; FONT-WEIGHT: bold +} +H3 { + FONT-STYLE: normal; FONT-FAMILY: helvetica, monaco, "MS Sans Serif", arial, sans-serif; FONT-WEIGHT: bold +} +H4 { + FONT-STYLE: normal; FONT-FAMILY: helvetica, monaco, "MS Sans Serif", arial, sans-serif; FONT-WEIGHT: bold +} +H5 { + FONT-STYLE: normal; FONT-FAMILY: helvetica, monaco, "MS Sans Serif", arial, sans-serif; FONT-WEIGHT: bold +} +H6 { + FONT-STYLE: normal; FONT-FAMILY: helvetica, monaco, "MS Sans Serif", arial, sans-serif; FONT-WEIGHT: bold +} +H1 { + TEXT-ALIGN: right; BACKGROUND-COLOR: transparent; COLOR: #900 +} +H3 { + BACKGROUND-COLOR: transparent; COLOR: #333 +} +TD.RFCbug { + TEXT-ALIGN: justify; BACKGROUND-COLOR: #000; WIDTH: 30px; HEIGHT: 30px; FONT-SIZE: x-small; VERTICAL-ALIGN: middle; TEXT-DECORATION: none; PADDING-TOP: 2px +} +TD.RFCbug SPAN.RFC { + FONT-FAMILY: monaco, charcoal, geneva, "MS Sans Serif", helvetica, verdana, sans-serif; COLOR: #666; FONT-WEIGHT: bold +} +TD.RFCbug SPAN.hotText { + TEXT-ALIGN: center; FONT-FAMILY: charcoal, monaco, geneva, "MS Sans Serif", helvetica, verdana, sans-serif; COLOR: #fff; FONT-WEIGHT: normal +} +TABLE.TOCbug { + WIDTH: 30px; HEIGHT: 15px +} +TD.TOCbug { + TEXT-ALIGN: center; BACKGROUND-COLOR: #900; WIDTH: 30px; HEIGHT: 15px; COLOR: #fff +} +TD.TOCbug A { + BACKGROUND-COLOR: transparent; FONT-FAMILY: monaco, charcoal, geneva, "MS Sans Serif", helvetica, sans-serif; COLOR: #fff; FONT-SIZE: x-small; FONT-WEIGHT: bold; TEXT-DECORATION: none +} +TD.header { + BACKGROUND-COLOR: #666; WIDTH: 33%; FONT-FAMILY: arial, helvetica, sans-serif; COLOR: #fff; FONT-SIZE: x-small; VERTICAL-ALIGN: top +} +TD.author { + MARGIN-LEFT: 4em; FONT-SIZE: x-small; FONT-WEIGHT: bold +} +TD.author-text { + FONT-SIZE: x-small +} +A.info { + Z-INDEX: 24; POSITION: relative; TEXT-DECORATION: none +} +A.info:hover { + Z-INDEX: 25; BACKGROUND-COLOR: #900; COLOR: #fff +} +A.info SPAN { + DISPLAY: none +} +A.info:hover SPAN.info { + BORDER-BOTTOM: #333 1px solid; POSITION: absolute; TEXT-ALIGN: left; BORDER-LEFT: #333 1px solid; PADDING-BOTTOM: 2px; BACKGROUND-COLOR: #eee; PADDING-LEFT: 2px; WIDTH: 15em; PADDING-RIGHT: 2px; DISPLAY: block; COLOR: #900; FONT-SIZE: smaller; BORDER-TOP: #333 1px solid; TOP: 2em; BORDER-RIGHT: #333 1px solid; PADDING-TOP: 2px; LEFT: -5em +} +A { + FONT-WEIGHT: bold +} +A:link { + BACKGROUND-COLOR: transparent; COLOR: #900 +} +A:visited { + BACKGROUND-COLOR: transparent; COLOR: #633 +} +A:active { + BACKGROUND-COLOR: transparent; COLOR: #633 +} +P { + MARGIN-LEFT: 2em; MARGIN-RIGHT: 2em +} +P.copyright { + FONT-SIZE: x-small +} +P.toc { + MARGIN-LEFT: 3em; FONT-SIZE: small; FONT-WEIGHT: bold +} +TABLE.toc { + BORDER-BOTTOM: 0px; BORDER-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 0px 0px 0px 3em; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; VERTICAL-ALIGN: text-top; BORDER-TOP: 0px; BORDER-RIGHT: 0px; PADDING-TOP: 0px +} +TD.toc { + FONT-SIZE: small; VERTICAL-ALIGN: text-top; FONT-WEIGHT: bold +} +OL.text { + MARGIN-LEFT: 2em; MARGIN-RIGHT: 2em +} +UL.text { + MARGIN-LEFT: 2em; MARGIN-RIGHT: 2em +} +LI { + MARGIN-LEFT: 3em +} +EM { + FONT-STYLE: italic +} +STRONG { + FONT-WEIGHT: bold +} +DFN { + FONT-STYLE: normal; FONT-WEIGHT: bold +} +CITE { + FONT-STYLE: normal; FONT-WEIGHT: normal +} +TT { + COLOR: #036 +} +TT { + FONT-FAMILY: "Courier New", Courier, monospace; FONT-SIZE: small +} +PRE { + FONT-FAMILY: "Courier New", Courier, monospace; FONT-SIZE: small +} +PRE DFN { + FONT-FAMILY: "Courier New", Courier, monospace; FONT-SIZE: small +} +PRE EM { + FONT-FAMILY: "Courier New", Courier, monospace; FONT-SIZE: small +} +PRE CITE { + FONT-FAMILY: "Courier New", Courier, monospace; FONT-SIZE: small +} +PRE SPAN { + FONT-FAMILY: "Courier New", Courier, monospace; FONT-SIZE: small +} +PRE { + TEXT-ALIGN: left; PADDING-BOTTOM: 4px; BACKGROUND-COLOR: #ccc; PADDING-LEFT: 4px; PADDING-RIGHT: 4px; COLOR: #000; PADDING-TOP: 4px +} +PRE DFN { + COLOR: #900 +} +PRE EM { + BACKGROUND-COLOR: #ffc; COLOR: #66f; FONT-WEIGHT: normal +} +PRE .key { + COLOR: #33c; FONT-WEIGHT: bold +} +PRE .id { + COLOR: #900 +} +PRE .str { + BACKGROUND-COLOR: #cff; COLOR: #000 +} +PRE .val { + COLOR: #066 +} +PRE .rep { + COLOR: #909 +} +PRE .oth { + BACKGROUND-COLOR: #fcf; COLOR: #000 +} +PRE .err { + BACKGROUND-COLOR: #fcc +} +TABLE.all { + TEXT-ALIGN: center; BORDER-RIGHT-WIDTH: 2px; BORDER-COLLAPSE: collapse; BORDER-TOP-WIDTH: 2px; BORDER-BOTTOM-WIDTH: 2px; FONT-SIZE: small; VERTICAL-ALIGN: top; BORDER-LEFT-WIDTH: 2px +} +TABLE.full { + TEXT-ALIGN: center; BORDER-RIGHT-WIDTH: 2px; BORDER-COLLAPSE: collapse; BORDER-TOP-WIDTH: 2px; BORDER-BOTTOM-WIDTH: 2px; FONT-SIZE: small; VERTICAL-ALIGN: top; BORDER-LEFT-WIDTH: 2px +} +TABLE.headers { + TEXT-ALIGN: center; BORDER-RIGHT-WIDTH: 2px; BORDER-COLLAPSE: collapse; BORDER-TOP-WIDTH: 2px; BORDER-BOTTOM-WIDTH: 2px; FONT-SIZE: small; VERTICAL-ALIGN: top; BORDER-LEFT-WIDTH: 2px +} +TABLE.none { + TEXT-ALIGN: center; BORDER-RIGHT-WIDTH: 2px; BORDER-COLLAPSE: collapse; BORDER-TOP-WIDTH: 2px; BORDER-BOTTOM-WIDTH: 2px; FONT-SIZE: small; VERTICAL-ALIGN: top; BORDER-LEFT-WIDTH: 2px +} +TABLE.all { + BORDER-BOTTOM-STYLE: solid; BORDER-BOTTOM-COLOR: black; BORDER-RIGHT-STYLE: solid; BORDER-TOP-COLOR: black; BORDER-TOP-STYLE: solid; BORDER-RIGHT-COLOR: black; BORDER-LEFT-STYLE: solid; BORDER-LEFT-COLOR: black +} +TABLE.full { + BORDER-BOTTOM-STYLE: solid; BORDER-BOTTOM-COLOR: black; BORDER-RIGHT-STYLE: solid; BORDER-TOP-COLOR: black; BORDER-TOP-STYLE: solid; BORDER-RIGHT-COLOR: black; BORDER-LEFT-STYLE: solid; BORDER-LEFT-COLOR: black +} +TABLE.headers { + BORDER-BOTTOM-STYLE: none; BORDER-RIGHT-STYLE: none; BORDER-TOP-STYLE: none; BORDER-LEFT-STYLE: none +} +TABLE.none { + BORDER-BOTTOM-STYLE: none; BORDER-RIGHT-STYLE: none; BORDER-TOP-STYLE: none; BORDER-LEFT-STYLE: none +} +TH { + BORDER-BOTTOM-COLOR: black; BORDER-RIGHT-WIDTH: 2px; BORDER-TOP-COLOR: black; BORDER-TOP-WIDTH: 2px; BORDER-BOTTOM-WIDTH: 3px; BORDER-RIGHT-COLOR: black; BORDER-LEFT-COLOR: black; BORDER-LEFT-WIDTH: 2px; FONT-WEIGHT: bold +} +TABLE.all TH { + BORDER-BOTTOM-STYLE: solid; BORDER-RIGHT-STYLE: solid; BORDER-TOP-STYLE: solid; BORDER-LEFT-STYLE: solid +} +TABLE.full TH { + BORDER-BOTTOM-STYLE: solid; BORDER-RIGHT-STYLE: solid; BORDER-TOP-STYLE: solid; BORDER-LEFT-STYLE: solid +} +TABLE.headers TH { + BORDER-BOTTOM-STYLE: solid; BORDER-RIGHT-STYLE: none; BORDER-TOP-STYLE: none; BORDER-LEFT-STYLE: none +} +TABLE.none TH { + BORDER-BOTTOM-STYLE: none; BORDER-RIGHT-STYLE: none; BORDER-TOP-STYLE: none; BORDER-LEFT-STYLE: none +} +TABLE.all TD { + BORDER-BOTTOM: #333 1px solid; BORDER-LEFT: #333 2px solid; BORDER-TOP: #333 1px solid; BORDER-RIGHT: #333 2px solid +} +TABLE.full TD { + BORDER-BOTTOM-STYLE: none; BORDER-RIGHT-STYLE: none; BORDER-TOP-STYLE: none; BORDER-LEFT-STYLE: none +} +TABLE.headers TD { + BORDER-BOTTOM-STYLE: none; BORDER-RIGHT-STYLE: none; BORDER-TOP-STYLE: none; BORDER-LEFT-STYLE: none +} +TABLE.none TD { + BORDER-BOTTOM-STYLE: none; BORDER-RIGHT-STYLE: none; BORDER-TOP-STYLE: none; BORDER-LEFT-STYLE: none +} +HR { + HEIGHT: 1px +} +HR.insert { + BORDER-BOTTOM: 0px; BORDER-LEFT: 0px; BACKGROUND-COLOR: #ccc; WIDTH: 80%; COLOR: #ccc; BORDER-TOP: 0px; BORDER-RIGHT: 0px +} +</STYLE> +</HEAD> +<BODY> +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE> +<TABLE border=0 cellSpacing=0 summary=layout cellPadding=0 width="66%"> + <TBODY> + <TR> + <TD> + <TABLE border=0 cellSpacing=1 summary=layout cellPadding=2 width="100%"> + <TBODY> + <TR> + <TD class=header>draft</TD> + <TD class=header>D. Balfanz</TD></TR> + <TR> + <TD class=header> </TD> + <TD class=header>B. de Medeiros</TD></TR> + <TR> + <TD class=header> </TD> + <TD class=header>Google</TD></TR> + <TR> + <TD class=header> </TD> + <TD class=header>D. Recordon</TD></TR> + <TR> + <TD class=header> </TD> + <TD class=header>Six Apart</TD></TR> + <TR> + <TD class=header> </TD> + <TD class=header>J. Smarr</TD></TR> + <TR> + <TD class=header> </TD> + <TD class=header>Plaxo</TD></TR> + <TR> + <TD class=header> </TD> + <TD class=header>A. Tom</TD></TR> + <TR> + <TD class=header> </TD> + <TD class=header>Yahoo</TD></TR> + <TR> + <TD class=header> </TD> + <TD class=header>January 7, +2009</TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE> +<H1><BR>OpenID OAuth Extension</H1> +<H3>Abstract</H3> +<P>This draft describes a mechanism to combine an OpenID authentication request +with the approval of an OAuth request token. </P><A name=toc></A><BR> +<HR> + +<H3>Table of Contents</H3> +<P class=toc><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#anchor1">1.</A> +Requirements notation<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#anchor2">2.</A> +Terminology<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#anchor3">3.</A> +Purpose of this Specification<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#anchor4">4.</A> +Overview<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#namespace">5.</A> +Extension Namespace<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#anchor5">6.</A> +Discovery<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#Registered">7.</A> +Before Requesting Authentication - Registration<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#AuthReq">8.</A> +Requesting Authentication<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#anchor6">9.</A> +Authorizing the OAuth Request<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#AuthResp">10.</A> +Responding to Authentication Requests<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#AuthTokenReq">11.</A> +Obtaining the Access Token<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#anchor7">12.</A> +General Considerations<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#anchor8">13.</A> +Security Considerations<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#rfc.references1">14.</A> +Normative References<BR><A +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#rfc.authors">§</A> +Authors' Addresses<BR></P><BR clear=all><A name=anchor1></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.1></A> +<H3>1. Requirements notation</H3> +<P>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in <A class=info +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#RFC2119">[RFC2119]<SPAN> +(</SPAN><SPAN class=info>Bradner, S., “Key words for use in RFCs to Indicate +Requirement Levels,” March 1997.</SPAN><SPAN>)</SPAN></A>. </P><A +name=anchor2></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.2></A> +<H3>2. Terminology</H3> +<P>Terms <EM>emphasized</EM> are pre-defined in either the <A class=info +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#OpenID">[OpenID]<SPAN> +(</SPAN><SPAN class=info>Openid.net, “OpenID Authentication 2.0 - Final,” +December 2007.</SPAN><SPAN>)</SPAN></A> or the <A class=info +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#OAuth">[OAuth]<SPAN> +(</SPAN><SPAN class=info>OAuth Core Workgroup, “OAuth Core 1.0,” +December 2007.</SPAN><SPAN>)</SPAN></A> specifications. </P> +<BLOCKQUOTE class=text> + <DL> + <DT>Combined Consumer:</DT> + <DD>A web service that is simultaneously an OpenID <EM>Relying Party + (RP)</EM> and an OAuth <EM>Consumer.</EM> </DD> + <DT>Combined Provider:</DT> + <DD>A web service that is simultaneously an OpenID <EM>Identity Provider + (OP)</EM> and an OAuth <EM>Service Provider (SP).</EM> </DD></DL></BLOCKQUOTE> +<P></P><A name=anchor3></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.3></A> +<H3>3. Purpose of this Specification</H3> +<P>The OpenID OAuth Extension describes how to make the OpenID Authentication +and OAuth Core specifications work well together. In its current form, it +addresses the use case where the OpenID Provider and OAuth Service Provider are +the same service. To provide good user experience, it is important to present, +to the user, a combined authentication and authorization screen for the two +protocols. </P> +<P>This extension describes how to embed an OAuth approval request into an +OpenID authentication request to permit combined user approval. For security +reasons, the OAuth access token is not returned in the OpenID authentication +response. Instead a mechanism to obtain the access token is provided. </P><A +name=anchor4></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.4></A> +<H3>4. Overview</H3> +<P>Unlike standard OAuth (<A class=info +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#OAuth">[OAuth]<SPAN> +(</SPAN><SPAN class=info>OAuth Core Workgroup, “OAuth Core 1.0,” +December 2007.</SPAN><SPAN>)</SPAN></A>), the OpenID OAuth Extension does +not provision request tokens in a server-to-server request from the Combined +Consumer to the request token endpoint at the Combined Provider. Instead, the +Combined Provider returns an already-approved request token to the Combined +Consumer as part of the OpenID authentication response. </P> +<P>The Combined Consumer then exchanges the request token for an access token at +the access token endpoint of the Combined Provider, following standard OAuth +practice. </P><A name=namespace></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.5></A> +<H3>5. Extension Namespace</H3> +<P>This protocol is an extension as defined by Section 12 of <A class=info +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#OpenID">[OpenID]<SPAN> +(</SPAN><SPAN class=info>Openid.net, “OpenID Authentication 2.0 - Final,” +December 2007.</SPAN><SPAN>)</SPAN></A>. The namespace URI for this +extension is "http://specs.openid.net/extensions/oauth/1.0". </P> +<P>All OpenID messages that contain an OpenID OAuth Extension element MUST +contain the following extension namespace declaration: </P> +<DIV style="WIDTH: 0px; DISPLAY: table; MARGIN-LEFT: 3em; MARGIN-RIGHT: auto"><PRE>openid.ns.<alias>=http://specs.openid.net/extensions/oauth/1.0</PRE></DIV> +<P>The actual extension namespace alias is determined by the party composing the +message in such a manner as to avoid conflicts among multiple extensions. +Throughout this document "oauth" is used as an example for the extension +namespace alias. </P><A name=anchor5></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.6></A> +<H3>6. Discovery</H3> +<P>Discovery of the OpenID OAuth Extension is achieved via the mechanism +described in <A class=info +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#OpenID">[OpenID]<SPAN> +(</SPAN><SPAN class=info>Openid.net, “OpenID Authentication 2.0 - Final,” +December 2007.</SPAN><SPAN>)</SPAN></A>. The OpenID OAuth Extension +namespace "http://specs.openid.net/extensions/oauth/1.0" SHOULD be listed as an +<xrd:Type> child element of the <xrd:Service> element in the XRDS +discovery document. </P><A name=Registered></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.7></A> +<H3>7. Before Requesting Authentication - Registration</H3> +<P>The Combined Consumer and the Combined Provider agree on a consumer key and +consumer secret (see <A class=info +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#OAuth">[OAuth]<SPAN> +(</SPAN><SPAN class=info>OAuth Core Workgroup, “OAuth Core 1.0,” +December 2007.</SPAN><SPAN>)</SPAN></A>). </P> +<P>The Combined Provider SHOULD in addition obtain, from the Combined Consumer, +a list of valid OpenID realms that the Combined Consumer may use in subsequent +authentication requests. The Combined Provider SHOULD verify that the Combined +Consumer is authorized to use those realms. </P><A name=AuthReq></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.8></A> +<H3>8. Requesting Authentication</H3> +<P>When requesting OpenID Authentication via the protocol mode "checkid_setup" +or "checkid_immediate", this extension can be used to request that the end user +authorize an OAuth access token at the same time as an OpenID authentication. +This is done by sending the following parameters as part of the OpenID request. +(Note that the use of "oauth" as part of the parameter names here and in +subsequent sections is just an example. See <A class=info +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#namespace">Section 5<SPAN> +(</SPAN><SPAN class=info>Extension Namespace</SPAN><SPAN>)</SPAN></A> for +details.) </P> +<BLOCKQUOTE class=text> + <DL> + <DT>openid.ns.oauth</DT> + <DD>REQUIRED. Value: "http://specs.openid.net/extensions/oauth/1.0". </DD> + <DT>openid.oauth.consumer</DT> + <DD>REQUIRED. Value: The consumer key agreed upon in <A class=info + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#Registered">Section 7<SPAN> + (</SPAN><SPAN class=info>Before Requesting Authentication - + Registration</SPAN><SPAN>)</SPAN></A>. </DD> + <DT>openid.oauth.scope</DT> + <DD>OPTIONAL. Value: A string that encodes, in a way possibly specific to + the Combined Provider, one or more scopes for the OAuth token expected in + the authentication response. </DD></DL></BLOCKQUOTE> +<P></P><A name=anchor6></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.9></A> +<H3>9. Authorizing the OAuth Request</H3> +<P>If the OpenID OAuth Extension is present in the authentication request, the +Combined Provider SHOULD verify that the consumer key passed in the request is +authorized to be used for the realm passed in the request. If this verification +succeeds, the Combined Provider SHOULD determine that delegation of access from +a user to the Combined Consumer has been requested. </P> +<P>The Combined Provider SHOULD NOT issue an approved request token unless it +has user consent to perform such delegation. </P><A name=AuthResp></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.10></A> +<H3>10. Responding to Authentication Requests</H3> +<P>If the OpenID authentication request cannot be fulfilled (either in failure +mode "setup_needed" or "cancel" as in Sections 10.2.1 and 10.2.2 of <A +class=info +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#OpenID">[OpenID]<SPAN> +(</SPAN><SPAN class=info>Openid.net, “OpenID Authentication 2.0 - Final,” +December 2007.</SPAN><SPAN>)</SPAN></A>) then the OAuth request SHOULD be +considered to fail and the Provider MUST NOT send any OpenID OAuth Extension +values in the response. </P> +<P>The remainder of this section specifies how to handle the OAuth request in +cases when the OpenID authentication response is a positive assertion (Section +10.1 of <A class=info +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#OpenID">[OpenID]<SPAN> +(</SPAN><SPAN class=info>Openid.net, “OpenID Authentication 2.0 - Final,” +December 2007.</SPAN><SPAN>)</SPAN></A>). </P> +<P>If the end user does wish to delegate access to the Combined Consumer, the +Combined Provider MUST include and MUST sign the following parameters. </P> +<BLOCKQUOTE class=text> + <DL> + <DT>openid.ns.oauth</DT> + <DD>REQUIRED. Identical value as defined in <A class=info + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#AuthReq">Section 8<SPAN> + (</SPAN><SPAN class=info>Requesting Authentication</SPAN><SPAN>)</SPAN></A>. + </DD> + <DT>openid.oauth.request_token</DT> + <DD>REQUIRED. A user-approved request token. </DD> + <DT>openid.oauth.scope</DT> + <DD>OPTIONAL. A string that encodes, in a way possibly specific to the + Combined Provider, one or more scopes that the returned request token is + valid for. This will typically indicate a subset of the scopes requested in + <A class=info + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#AuthReq">Section 8<SPAN> + (</SPAN><SPAN class=info>Requesting Authentication</SPAN><SPAN>)</SPAN></A>. + </DD></DL></BLOCKQUOTE> +<P>To note that the OAuth Authorization was declined or not valid, the Combined +Provider SHALL only respond with the parameter "openid.ns.oauth". </P><A +name=AuthTokenReq></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.11></A> +<H3>11. Obtaining the Access Token</H3> +<P>To exchange the request token for an access token, the Combined Consumer +follows Section 6.3.1 of <A class=info +href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#OAuth">[OAuth]<SPAN> +(</SPAN><SPAN class=info>OAuth Core Workgroup, “OAuth Core 1.0,” +December 2007.</SPAN><SPAN>)</SPAN></A>, i.e., it sends an access token +request to the access token endpoint of the Combined Provider. It SHALL use the +following values to create the OAuth access token request: </P> +<BLOCKQUOTE class=text> + <DL> + <DT>consumer key</DT> + <DD>Combined Consumers use the consumer key they established with the + Combined Provider in <A class=info + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#Registered">Section 7<SPAN> + (</SPAN><SPAN class=info>Before Requesting Authentication - + Registration</SPAN><SPAN>)</SPAN></A>. </DD> + <DT>consumer secret</DT> + <DD>Combined Consumers use the consumer secret they established with the + Combined Provider in <A class=info + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#Registered">Section 7<SPAN> + (</SPAN><SPAN class=info>Before Requesting Authentication - + Registration</SPAN><SPAN>)</SPAN></A>. </DD> + <DT>OAuth token</DT> + <DD>Combined Consumers use the request token obtained in <A class=info + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#AuthResp">Section 10<SPAN> + (</SPAN><SPAN class=info>Responding to Authentication + Requests</SPAN><SPAN>)</SPAN></A>. </DD> + <DT>OAuth token secret</DT> + <DD>Combined Consumers use the empty string. </DD></DL></BLOCKQUOTE> +<P></P> +<P>The Combined Provider follows Section 6.3.2 to verify the request and either +issue the access token or send an error response. </P><A name=anchor7></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.12></A> +<H3>12. General Considerations</H3> +<P>The proposal takes the approach to insulate each protocol from the other, +both for backwards compatibility as well as to enable OpenID and OAuth to evolve +and incorporate additional features without requiring reviews of the combined +usage described here. In particular: </P> +<BLOCKQUOTE class=text> + <DL> + <DT>OpenID full compatibility</DT> + <DD>The OpenID identity provider (OP) MAY safely announce the endpoint + supporting the OpenID OAuth Extension to all relying parties, whether or not + they support the extension as well. The use of a separate service-type + announcement for Combined Providers endpoints provides a mechanism for + auto-discovery of OAuth capabilities by RPs. </DD> + <DT>OAuth token compatibility</DT> + <DD>The OAuth tokens approved via this mechanism MAY be used identically as + tokens acquired through alternative mechanisms (e.g., via standard OAuth) + without requiring special considerations either because of functionality or + security reasons. </DD></DL></BLOCKQUOTE> +<P></P><A name=anchor8></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE><A +name=rfc.section.13></A> +<H3>13. Security Considerations</H3> +<P>This proposal composes protocols that provide security services +(authentication in the case of OpenID, authorization in the case of OAuth) with +the intention that the combined protocol provides both services simultaneously. +Since security is not a property generally preserved by composition, the design +takes the approach of encapsulating the OAuth flow within OpenID in a modular +way, and applies the general rule-of-thumb of NOT introducing reliance on the +security properties of one protocol for the correctness of the other. +Ultimately, only public scrutiny and review can incrementally provide confidence +that the approach described here is sound from a security perspective. </P> +<P>The following security principles are reflected in this design: </P> +<BLOCKQUOTE class=text> + <DL> + <DT>No long-term OAuth secrets hit the browser</DT> + <DD>The OAuth protocol was designed so that browser-mediated communication + is not used to transfer long-term secrets or capabilities to access + data.(Instead, server-to-server calls are used to exchange such secrets). + Combined Providers can preserve this property by making the request_token + short-lived, since the request token will be exchanged for an access token + and secret over a server-to-server call. </DD> + <DT>Imposters cannot retrieve the OAuth access token</DT> + <DD>While it is possible for a malicious party to fake an OpenID request, + including an OpenID request that includes the OpenID OAuth Extension (the + request is not signed, and knowledge of the consumer key and realm is + sufficient to cause the Combined Provider to display an authorization page + for that realm/consumer), that malicious party would have to have knowledge + of the consumer secret to exchange the request token for an access token. + Note that while secure under reasonable threat models, this is different + from standard OAuth: In standard OAuth, one needs knowledge of both the + consumer key and consumer secret (or, alternatively, of a request token + obtained through knowledge of the consumer key and secret) to cause the + Service Provider to display an authorization page for that consumer. +</DD></DL></BLOCKQUOTE> +<P></P><A name=rfc.references1></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE> +<H3>14. Normative References</H3> +<TABLE border=0 width="99%"> + <TBODY> + <TR> + <TD class=author-text vAlign=top><A name=OAuth>[OAuth]</A></TD> + <TD class=author-text><A href="mailto:spec@oauth.net">OAuth Core + Workgroup</A>, “<A href="http://oauth.net/core/1.0/">OAuth Core 1.0</A>,” + December 2007 (<A href="http://oauth.net/core/1.0/">HTML</A>).</TD></TR> + <TR> + <TD class=author-text vAlign=top><A name=OpenID>[OpenID]</A></TD> + <TD class=author-text><A href="mailto:specs@openid.net">Openid.net</A>, + “<A href="http://openid.net/specs/openid-authentication-2_0.html">OpenID + Authentication 2.0 - Final</A>,” December 2007 (<A + href="http://openid.net/specs/openid-authentication-2_0.html">HTML</A>, <A + href="http://openid.net/specs/openid-authentication-2_0.txt">TXT</A>).</TD></TR> + <TR> + <TD class=author-text vAlign=top><A name=RFC2119>[RFC2119]</A></TD> + <TD class=author-text><A href="mailto:sob@harvard.edu">Bradner, S.</A>, + “<A href="http://tools.ietf.org/html/rfc2119">Key words for use in RFCs to + Indicate Requirement Levels</A>,” BCP 14, RFC 2119, + March 1997 (<A href="ftp://ftp.isi.edu/in-notes/rfc2119.txt">TXT</A>, + <A href="http://xml.resource.org/public/rfc/html/rfc2119.html">HTML</A>, + <A + href="http://xml.resource.org/public/rfc/xml/rfc2119.xml">XML</A>).</TD></TR></TBODY></TABLE><A +name=rfc.authors></A><BR> +<HR> + +<TABLE class=TOCbug cellSpacing=2 summary=layout cellPadding=0 align=right> + <TBODY> + <TR> + <TD class=TOCbug><A + href="http://step2.googlecode.com/svn/spec/openid_oauth_extension/latest/openid_oauth_extension.html#toc"> TOC </A></TD></TR></TBODY></TABLE> +<H3>Authors' Addresses</H3> +<TABLE border=0 cellSpacing=0 cellPadding=0 width="99%"> + <TBODY> + <TR> + <TD class=author-text> </TD> + <TD class=author-text>Dirk Balfanz (editor)</TD></TR> + <TR> + <TD class=author-text> </TD> + <TD class=author-text>Google, Inc.</TD></TR> + <TR> + <TD class=author align=right>Email: </TD> + <TD class=author-text><A + href="mailto:balfanz@google.com">balfanz@google.com</A></TD></TR> + <TR cellpadding="3"> + <TD> </TD> + <TD> </TD></TR> + <TR> + <TD class=author-text> </TD> + <TD class=author-text>Breno de Medeiros (editor)</TD></TR> + <TR> + <TD class=author-text> </TD> + <TD class=author-text>Google, Inc.</TD></TR> + <TR> + <TD class=author align=right>Email: </TD> + <TD class=author-text><A + href="mailto:breno@google.com">breno@google.com</A></TD></TR> + <TR cellpadding="3"> + <TD> </TD> + <TD> </TD></TR> + <TR> + <TD class=author-text> </TD> + <TD class=author-text>David Recordon (editor)</TD></TR> + <TR> + <TD class=author-text> </TD> + <TD class=author-text>Six Apart, Ltd.</TD></TR> + <TR> + <TD class=author align=right>Email: </TD> + <TD class=author-text><A + href="mailto:david@sixapart.com">david@sixapart.com</A></TD></TR> + <TR cellpadding="3"> + <TD> </TD> + <TD> </TD></TR> + <TR> + <TD class=author-text> </TD> + <TD class=author-text>Joseph Smarr (editor)</TD></TR> + <TR> + <TD class=author-text> </TD> + <TD class=author-text>Plaxo, Inc.</TD></TR> + <TR> + <TD class=author align=right>Email: </TD> + <TD class=author-text><A + href="mailto:joseph@plaxo.com">joseph@plaxo.com</A></TD></TR> + <TR cellpadding="3"> + <TD> </TD> + <TD> </TD></TR> + <TR> + <TD class=author-text> </TD> + <TD class=author-text>Allen Tom (editor)</TD></TR> + <TR> + <TD class=author-text> </TD> + <TD class=author-text>Yahoo!, Inc.</TD></TR> + <TR> + <TD class=author align=right>Email: </TD> + <TD class=author-text><A + href="mailto:atom@yahoo-inc.com">atom@yahoo-inc.com</A></TD></TR></TBODY></TABLE></BODY></HTML> diff --git a/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeRequest.cs b/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeRequest.cs index 84fdc36..8859c10 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeRequest.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeRequest.cs @@ -27,6 +27,8 @@ namespace DotNetOpenAuth.ApplicationBlock.CustomExtensions { get { return Enumerable.Empty<string>(); } } + public bool IsSignedByRemoteParty { get; set; } + #endregion #region IMessage Members diff --git a/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeResponse.cs b/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeResponse.cs index 3fae7d8..1e6748c 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeResponse.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeResponse.cs @@ -46,6 +46,8 @@ namespace DotNetOpenAuth.ApplicationBlock.CustomExtensions { get { return Enumerable.Empty<string>(); } } + public bool IsSignedByRemoteParty { get; set; } + #endregion #region IMessage Members diff --git a/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj b/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj index 976a325..570d91f 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj +++ b/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj @@ -59,7 +59,11 @@ <Compile Include="CustomExtensions\AcmeRequest.cs" /> <Compile Include="CustomExtensions\AcmeResponse.cs" /> <Compile Include="GoogleConsumer.cs" /> + <Compile Include="OAuthIdentity.cs" /> + <Compile Include="OAuthPrincipal.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Provider\AnonymousIdentifierProviderBase.cs" /> + <Compile Include="Provider\AuthenticationRequestExtensions.cs" /> <Compile Include="TwitterConsumer.cs" /> <Compile Include="Util.cs" /> </ItemGroup> diff --git a/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs b/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs index bcdb477..4d3ce13 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs @@ -268,7 +268,7 @@ namespace DotNetOpenAuth.ApplicationBlock { /// </summary> /// <param name="scope">The scope, which may include one or several Google applications.</param> /// <returns>A space-delimited list of URIs for the requested Google applications.</returns> - private static string GetScopeUri(Applications scope) { + public static string GetScopeUri(Applications scope) { return string.Join(" ", Util.GetIndividualFlags(scope).Select(app => DataScopeUris[(Applications)app]).ToArray()); } } diff --git a/samples/DotNetOpenAuth.ApplicationBlock/OAuthIdentity.cs b/samples/DotNetOpenAuth.ApplicationBlock/OAuthIdentity.cs new file mode 100644 index 0000000..ea9ec0b --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/OAuthIdentity.cs @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthIdentity.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ApplicationBlock { + using System; + using System.Runtime.InteropServices; + using System.Security.Principal; + + /// <summary> + /// Represents an OAuth consumer that is impersonating a known user on the system. + /// </summary> + [Serializable] + [ComVisible(true)] + internal class OAuthIdentity : IIdentity { + /// <summary> + /// Initializes a new instance of the <see cref="OAuthIdentity"/> class. + /// </summary> + /// <param name="username">The username.</param> + internal OAuthIdentity(string username) { + if (String.IsNullOrEmpty(username)) { + throw new ArgumentNullException("username"); + } + + this.Name = username; + } + + #region IIdentity Members + + /// <summary> + /// Gets the type of authentication used. + /// </summary> + /// <value>The constant "OAuth"</value> + /// <returns> + /// The type of authentication used to identify the user. + /// </returns> + public string AuthenticationType { + get { return "OAuth"; } + } + + /// <summary> + /// Gets a value indicating whether the user has been authenticated. + /// </summary> + /// <value>The value <c>true</c></value> + /// <returns>true if the user was authenticated; otherwise, false. + /// </returns> + public bool IsAuthenticated { + get { return true; } + } + + /// <summary> + /// Gets the name of the user who authorized the OAuth token the consumer is using for authorization. + /// </summary> + /// <returns> + /// The name of the user on whose behalf the code is running. + /// </returns> + public string Name { get; private set; } + + #endregion + } +} diff --git a/samples/DotNetOpenAuth.ApplicationBlock/OAuthPrincipal.cs b/samples/DotNetOpenAuth.ApplicationBlock/OAuthPrincipal.cs new file mode 100644 index 0000000..88f3b83 --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/OAuthPrincipal.cs @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthPrincipal.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ApplicationBlock { + using System; + using System.Linq; + using System.Runtime.InteropServices; + using System.Security.Principal; + + /// <summary> + /// Represents an OAuth consumer that is impersonating a known user on the system. + /// </summary> + [Serializable] + [ComVisible(true)] + internal class OAuthPrincipal : IPrincipal { + /// <summary> + /// The roles this user belongs to. + /// </summary> + private string[] roles; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. + /// </summary> + /// <param name="identity">The identity.</param> + /// <param name="roles">The roles this user belongs to.</param> + internal OAuthPrincipal(OAuthIdentity identity, string[] roles) { + this.Identity = identity; + this.roles = roles; + } + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. + /// </summary> + /// <param name="username">The username.</param> + /// <param name="roles">The roles this user belongs to.</param> + internal OAuthPrincipal(string username, string[] roles) + : this(new OAuthIdentity(username), roles) { + } + + #region IPrincipal Members + + /// <summary> + /// Gets the identity of the current principal. + /// </summary> + /// <value></value> + /// <returns> + /// The <see cref="T:System.Security.Principal.IIdentity"/> object associated with the current principal. + /// </returns> + public IIdentity Identity { get; private set; } + + /// <summary> + /// Determines whether the current principal belongs to the specified role. + /// </summary> + /// <param name="role">The name of the role for which to check membership.</param> + /// <returns> + /// true if the current principal is a member of the specified role; otherwise, false. + /// </returns> + public bool IsInRole(string role) { + return this.roles.Contains(role); + } + + #endregion + } +} diff --git a/samples/DotNetOpenAuth.ApplicationBlock/Provider/AnonymousIdentifierProviderBase.cs b/samples/DotNetOpenAuth.ApplicationBlock/Provider/AnonymousIdentifierProviderBase.cs new file mode 100644 index 0000000..1df7267 --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/Provider/AnonymousIdentifierProviderBase.cs @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------- +// <copyright file="AnonymousIdentifierProviderBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ApplicationBlock.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + + public abstract class AnonymousIdentifierProviderBase { + private int newSaltLength = 20; + + /// <summary> + /// Initializes a new instance of the <see cref="AnonymousIdentifierProviderBase"/> class. + /// </summary> + /// <param name="baseIdentifier">The base URI on which to append the anonymous part.</param> + public AnonymousIdentifierProviderBase(Uri baseIdentifier) { + if (baseIdentifier == null) { + throw new ArgumentNullException("baseIdentifier"); + } + + this.Hasher = HashAlgorithm.Create("SHA256"); + this.Encoder = Encoding.UTF8; + this.BaseIdentifier = baseIdentifier; + } + + public Uri BaseIdentifier { get; private set; } + + protected HashAlgorithm Hasher { get; private set; } + + protected Encoding Encoder { get; private set; } + + protected int NewSaltLength { + get { + return this.newSaltLength; + } + + set { + if (value <= 0) { + throw new ArgumentOutOfRangeException("value"); + } + + this.newSaltLength = value; + } + } + + #region IAnonymousIdentifierProvider Members + + public Uri GetAnonymousIdentifier(Identifier localIdentifier, Realm relyingPartyRealm) { + byte[] salt = this.GetHashSaltForLocalIdentifier(localIdentifier); + string valueToHash = localIdentifier + "#" + (relyingPartyRealm ?? string.Empty); + byte[] valueAsBytes = this.Encoder.GetBytes(valueToHash); + byte[] bytesToHash = new byte[valueAsBytes.Length + salt.Length]; + valueAsBytes.CopyTo(bytesToHash, 0); + salt.CopyTo(bytesToHash, valueAsBytes.Length); + byte[] hash = this.Hasher.ComputeHash(bytesToHash); + string base64Hash = Convert.ToBase64String(hash); + Uri anonymousIdentifier = this.AppendIdentifiers(this.BaseIdentifier, base64Hash); + return anonymousIdentifier; + } + + #endregion + + protected virtual byte[] GetNewSalt() { + // We COULD use a crypto random function, but for a salt it seems overkill. + return Util.GetNonCryptoRandomData(this.NewSaltLength); + } + + protected Uri AppendIdentifiers(Uri baseIdentifier, string uriHash) { + if (baseIdentifier == null) { + throw new ArgumentNullException("baseIdentifier"); + } + if (String.IsNullOrEmpty(uriHash)) { + throw new ArgumentNullException("uriHash"); + } + + if (string.IsNullOrEmpty(baseIdentifier.Query)) { + // The uriHash will appear on the path itself. + string pathEncoded = Uri.EscapeUriString(uriHash.Replace('/', '_')); + return new Uri(baseIdentifier, pathEncoded); + } else { + // The uriHash will appear on the query string. + string dataEncoded = Uri.EscapeDataString(uriHash); + return new Uri(baseIdentifier + dataEncoded); + } + } + + /// <summary> + /// Gets the salt to use for generating an anonymous identifier for a given OP local identifier. + /// </summary> + /// <param name="localIdentifier">The OP local identifier.</param> + /// <returns>The salt to use in the hash.</returns> + /// <remarks> + /// It is important that this method always return the same value for a given + /// <paramref name="localIdentifier"/>. + /// New salts can be generated for local identifiers without previously assigned salt + /// values by calling <see cref="GetNewSalt"/> or by a custom method. + /// </remarks> + protected abstract byte[] GetHashSaltForLocalIdentifier(Identifier localIdentifier); + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() { + Contract.Invariant(this.Hasher != null); + Contract.Invariant(this.Encoder != null); + Contract.Invariant(this.BaseIdentifier != null); + Contract.Invariant(this.NewHashLength > 0); + } +#endif + } +} diff --git a/samples/DotNetOpenAuth.ApplicationBlock/Provider/AuthenticationRequestExtensions.cs b/samples/DotNetOpenAuth.ApplicationBlock/Provider/AuthenticationRequestExtensions.cs new file mode 100644 index 0000000..a737d30 --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/Provider/AuthenticationRequestExtensions.cs @@ -0,0 +1,38 @@ +namespace DotNetOpenAuth.ApplicationBlock.Provider { + using System; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Provider; + + public static class AuthenticationRequestExtensions { + /// <summary> + /// Removes all personally identifiable information from the positive assertion. + /// </summary> + /// <param name="request">The incoming authentication request.</param> + /// <param name="localIdentifier">The OP local identifier, before the anonymous hash is applied to it.</param> + /// <param name="anonymousIdentifierProvider">The anonymous identifier provider.</param> + /// <param name="pairwiseUnique">if set to <c>true</c> the anonymous identifier will be unique to the requesting relying party's realm.</param> + /// <remarks> + /// The openid.claimed_id and openid.identity values are hashed. + /// </remarks> + public static void ScrubPersonallyIdentifiableInformation(this IAuthenticationRequest request, Identifier localIdentifier, AnonymousIdentifierProviderBase anonymousIdentifierProvider, bool pairwiseUnique) { + if (request == null) { + throw new ArgumentNullException("request"); + } + if (!request.IsDirectedIdentity) { + throw new InvalidOperationException("This operation is supported only under identifier select (directed identity) scenarios."); + } + if (anonymousIdentifierProvider == null) { + throw new ArgumentNullException("anonymousIdentifierProvider"); + } + if (localIdentifier == null) { + throw new ArgumentNullException("localIdentifier"); + } + + // When generating the anonymous identifiers, the openid.identity and openid.claimed_id + // will always end up with matching values. + var anonymousIdentifier = anonymousIdentifierProvider.GetAnonymousIdentifier(localIdentifier, pairwiseUnique ? request.Realm : null); + request.ClaimedIdentifier = anonymousIdentifier; + request.LocalIdentifier = anonymousIdentifier; + } + } +} diff --git a/samples/DotNetOpenAuth.ApplicationBlock/Util.cs b/samples/DotNetOpenAuth.ApplicationBlock/Util.cs index ea7da97..8a188ac 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/Util.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/Util.cs @@ -5,6 +5,8 @@ using DotNetOpenAuth.Messaging; internal static class Util { + internal static readonly Random NonCryptoRandomDataGenerator = new Random(); + /// <summary> /// Enumerates through the individual set bits in a flag enum. /// </summary> @@ -28,6 +30,17 @@ } /// <summary> + /// Gets a buffer of random data (not cryptographically strong). + /// </summary> + /// <param name="length">The length of the sequence to generate.</param> + /// <returns>The generated values, which may contain zeros.</returns> + internal static byte[] GetNonCryptoRandomData(int length) { + byte[] buffer = new byte[length]; + NonCryptoRandomDataGenerator.NextBytes(buffer); + return buffer; + } + + /// <summary> /// Copies the contents of one stream to another. /// </summary> /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param> diff --git a/samples/OpenIdOfflineProvider/App.config b/samples/OpenIdOfflineProvider/App.config new file mode 100644 index 0000000..cd04b13 --- /dev/null +++ b/samples/OpenIdOfflineProvider/App.config @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <configSections> + <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth" requirePermission="false" allowLocation="true"/> + <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" requirePermission="false"/> + </configSections> + <dotNetOpenAuth> + <messaging> + <untrustedWebRequest> + <whitelistHosts> + <!-- since this is a tool designed for local use and will often be used with localhost --> + <add name="localhost" /> + </whitelistHosts> + </untrustedWebRequest> + </messaging> + </dotNetOpenAuth> + <log4net> + <appender name="TextBoxAppender" type="log4net.Appender.TextWriterAppender"> + <immediateFlush value="true" /> + <layout type="log4net.Layout.PatternLayout"> + <conversionPattern value="%-5level %message%newline" /> + </layout> + </appender> + <!-- Setup the root category, add the appenders and set the default level --> + <root> + <level value="Info" /> + <appender-ref ref="TextBoxAppender" /> + </root> + <!-- Specify the level for some specific categories --> + <logger name="DotNetOpenAuth"> + <level value="INFO" /> + </logger> + <logger name="DotNetOpenAuth.OpenId.ChannelElements.SigningBindingElement"> + <level value="WARN" /> + </logger> + </log4net> +</configuration>
\ No newline at end of file diff --git a/samples/OpenIdOfflineProvider/App.xaml b/samples/OpenIdOfflineProvider/App.xaml new file mode 100644 index 0000000..a23f243 --- /dev/null +++ b/samples/OpenIdOfflineProvider/App.xaml @@ -0,0 +1,8 @@ +<Application x:Class="DotNetOpenAuth.OpenIdOfflineProvider.App" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + StartupUri="MainWindow.xaml"> + <Application.Resources> + + </Application.Resources> +</Application> diff --git a/samples/OpenIdOfflineProvider/App.xaml.cs b/samples/OpenIdOfflineProvider/App.xaml.cs new file mode 100644 index 0000000..ed0f25a --- /dev/null +++ b/samples/OpenIdOfflineProvider/App.xaml.cs @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------- +// <copyright file="App.xaml.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Data; + using System.Linq; + using System.Windows; + using log4net; + using log4net.Core; + + /// <summary> + /// Interaction logic for App.xaml + /// </summary> + public partial class App : Application { + /// <summary> + /// Message logger. + /// </summary> + internal static ILog Logger = log4net.LogManager.GetLogger(typeof(App)); + + /// <summary> + /// Initializes a new instance of the <see cref="App"/> class. + /// </summary> + public App() { + log4net.Config.XmlConfigurator.Configure(); + } + } +} diff --git a/samples/OpenIdOfflineProvider/CheckIdWindow.xaml b/samples/OpenIdOfflineProvider/CheckIdWindow.xaml new file mode 100644 index 0000000..29e5126 --- /dev/null +++ b/samples/OpenIdOfflineProvider/CheckIdWindow.xaml @@ -0,0 +1,84 @@ +<Window x:Class="DotNetOpenAuth.OpenIdOfflineProvider.CheckIdWindow" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + Title="Authentication request" Height="345" Width="379" ShowInTaskbar="False"> + <DockPanel Margin="12"> + <TextBlock DockPanel.Dock="Top" TextWrapping="Wrap">An authentication request has been received. How do you want to proceed?</TextBlock> + <Expander DockPanel.Dock="Top" Header="View request details" IsExpanded="True"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Label>Immediate?</Label> + <Label Grid.Column="1" Name="immediateModeLabel" Content="Yes" /> + <Label Grid.Column="1" Name="setupModeLabel" Content="No" /> + <Label Grid.Row="1">Realm</Label> + <Label Grid.Row="1" Grid.Column="1" Name="realmLabel" /> + <!--<Label Grid.Row="2">Callback</Label> + <Label Grid.Row="2" Grid.Column="1" Name="callbackLabel" />--> + <Label Grid.Row="3">Discoverable</Label> + <Label Grid.Row="3" Grid.Column="1" Name="discoverableYesLabel" Content="Yes" /> + <Label Grid.Row="3" Grid.Column="1" Name="discoverableNoLabel" Content="No" /> + <!--<Label Grid.Row="4">Shared association?</Label> + <Label Grid.Row="4" Grid.Column="1" Name="sharedAssociationLabel" Content="Yes" /> + <Label Grid.Row="4" Grid.Column="1" Name="privateAssociationLabel" Content="No" />--> + </Grid> + </Expander> + <StackPanel DockPanel.Dock="Bottom" HorizontalAlignment="Right" Orientation="Horizontal" Margin="0,12,0,0"> + <Button Name="sendResponseButton" IsDefault="True" Margin="0,0,4,0" Click="sendResponseButton_Click">Send response</Button> + <Button Name="cancelButton" IsCancel="True">Cancel</Button> + </StackPanel> + + <TabControl Name="tabControl1"> + <TabItem Header="Positive assertion" Name="positiveTab"> + <StackPanel> + <TextBlock TextWrapping="Wrap">You may customize the positive assertion if you wish.</TextBlock> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <Label>Claimed identifier</Label> + <TextBox Grid.Column="1" Name="claimedIdentifierBox" /> + <Label Grid.Row="1">OP Local identifier</Label> + <TextBox Grid.Column="1" Grid.Row="1" Name="localIdentifierBox" /> + <!--<Label Grid.Row="2">Association</Label> + <WrapPanel Grid.Row="2" Grid.Column="1" VerticalAlignment="Center"> + <RadioButton Margin="0,0,12,0" GroupName="AssociationType">Shared</RadioButton> + <RadioButton GroupName="AssociationType">Private</RadioButton> + </WrapPanel>--> + </Grid> + </StackPanel> + </TabItem> + <TabItem Header="Negative assertion" Name="negativeTab"> + <TextBlock TextWrapping="Wrap">There is nothing to customize in a negative assertion.</TextBlock> + </TabItem> + <!--<TabItem Header="Error" Name="errorTab" > + <StackPanel> + <TextBlock TextWrapping="Wrap">What message do you want to send describing the simulated error?</TextBlock> + <TextBox TextWrapping="Wrap" /> + </StackPanel> + </TabItem> + <TabItem Header="Invalid" Name="invalidTab"> + <StackPanel> + <TextBlock TextWrapping="Wrap">This tab is useful for testing a relying party's resiliance to invalid responses.</TextBlock> + <TextBlock TextWrapping="Wrap">But it's not implemented yet. :)</TextBlock> + </StackPanel> + </TabItem>--> + </TabControl> + </DockPanel> +</Window> diff --git a/samples/OpenIdOfflineProvider/CheckIdWindow.xaml.cs b/samples/OpenIdOfflineProvider/CheckIdWindow.xaml.cs new file mode 100644 index 0000000..597f72f --- /dev/null +++ b/samples/OpenIdOfflineProvider/CheckIdWindow.xaml.cs @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------- +// <copyright file="CheckIdWindow.xaml.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using System.Windows; + using System.Windows.Controls; + using System.Windows.Data; + using System.Windows.Documents; + using System.Windows.Input; + using System.Windows.Media; + using System.Windows.Media.Imaging; + using System.Windows.Shapes; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// Interaction logic for CheckIdWindow.xaml + /// </summary> + public partial class CheckIdWindow : Window { + /// <summary> + /// Initializes a new instance of the <see cref="CheckIdWindow"/> class. + /// </summary> + /// <param name="provider">The OpenID Provider host.</param> + /// <param name="request">The incoming authentication request.</param> + private CheckIdWindow(HostedProvider provider, IAuthenticationRequest request) { + Contract.Requires(request != null); + + InitializeComponent(); + + // Initialize the window with appropriate values. + this.realmLabel.Content = request.Realm; + this.immediateModeLabel.Visibility = request.Immediate ? Visibility.Visible : Visibility.Collapsed; + this.setupModeLabel.Visibility = request.Immediate ? Visibility.Collapsed : Visibility.Visible; + + bool isRPDiscoverable = request.IsReturnUrlDiscoverable(provider.Provider.Channel.WebRequestHandler); + this.discoverableYesLabel.Visibility = isRPDiscoverable ? Visibility.Visible : Visibility.Collapsed; + this.discoverableNoLabel.Visibility = isRPDiscoverable ? Visibility.Collapsed : Visibility.Visible; + + if (request.IsDirectedIdentity) { + this.claimedIdentifierBox.Text = provider.UserIdentityPageBase.AbsoluteUri; + this.localIdentifierBox.Text = provider.UserIdentityPageBase.AbsoluteUri; + } else { + this.claimedIdentifierBox.Text = request.ClaimedIdentifier; + this.localIdentifierBox.Text = request.LocalIdentifier; + } + } + + /// <summary> + /// Processes an authentication request by a popup window. + /// </summary> + /// <param name="provider">The OpenID Provider host.</param> + /// <param name="request">The incoming authentication request.</param> + internal static void ProcessAuthentication(HostedProvider provider, IAuthenticationRequest request) { + Contract.Requires(provider != null); + Contract.Requires(request != null); + + var window = new CheckIdWindow(provider, request); + bool? result = window.ShowDialog(); + + // If the user pressed Esc or cancel, just send a negative assertion. + if (!result.HasValue || !result.Value) { + request.IsAuthenticated = false; + return; + } + + request.IsAuthenticated = window.tabControl1.SelectedItem == window.positiveTab; + if (request.IsAuthenticated.Value) { + request.ClaimedIdentifier = window.claimedIdentifierBox.Text; + request.LocalIdentifier = window.localIdentifierBox.Text; + } + } + + /// <summary> + /// Handles the Click event of the sendResponseButton 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 sendResponseButton_Click(object sender, RoutedEventArgs e) { + this.DialogResult = true; + Close(); + } + } +} diff --git a/samples/OpenIdOfflineProvider/HostedProvider.cs b/samples/OpenIdOfflineProvider/HostedProvider.cs new file mode 100644 index 0000000..3d50dd9 --- /dev/null +++ b/samples/OpenIdOfflineProvider/HostedProvider.cs @@ -0,0 +1,253 @@ +//----------------------------------------------------------------------- +// <copyright file="HostedProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.IO; + using System.Linq; + using System.Net; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + using log4net; + + /// <summary> + /// The OpenID Provider host. + /// </summary> + internal class HostedProvider : IDisposable { + /// <summary> + /// The path to the Provider Endpoint. + /// </summary> + private const string ProviderPath = "/provider"; + + /// <summary> + /// The path to the OP Identifier. + /// </summary> + private const string OPIdentifierPath = "/"; + + /// <summary> + /// The URL path with which all user identities must start. + /// </summary> + private const string UserIdentifierPath = "/user/"; + + /// <summary> + /// The <see cref="OpenIdProvider"/> instance that processes incoming requests. + /// </summary> + private OpenIdProvider provider = new OpenIdProvider(new StandardProviderApplicationStore()); + + /// <summary> + /// The HTTP listener that acts as the OpenID Provider socket. + /// </summary> + private HttpHost httpHost; + + /// <summary> + /// Initializes a new instance of the <see cref="HostedProvider"/> class. + /// </summary> + internal HostedProvider() { + } + + /// <summary> + /// Gets a value indicating whether this instance is running. + /// </summary> + /// <value> + /// <c>true</c> if this instance is running; otherwise, <c>false</c>. + /// </value> + internal bool IsRunning { + get { return this.httpHost != null; } + } + + /// <summary> + /// Gets the <see cref="OpenIdProvider"/> instance that processes incoming requests. + /// </summary> + internal OpenIdProvider Provider { + get { return this.provider; } + } + + /// <summary> + /// Gets or sets the delegate that handles authentication requests. + /// </summary> + internal Action<HttpRequestInfo, HttpListenerResponse> ProcessRequest { get; set; } + + /// <summary> + /// Gets the provider endpoint. + /// </summary> + internal Uri ProviderEndpoint { + get { + Contract.Requires(this.IsRunning); + return new Uri(this.httpHost.BaseUri, ProviderPath); + } + } + + /// <summary> + /// Gets the base URI that all user identities must start with. + /// </summary> + internal Uri UserIdentityPageBase { + get { + Contract.Requires(this.IsRunning); + return new Uri(this.httpHost.BaseUri, UserIdentifierPath); + } + } + + /// <summary> + /// Gets the OP identifier. + /// </summary> + internal Uri OPIdentifier { + get { + Contract.Requires(this.IsRunning); + return new Uri(this.httpHost.BaseUri, OPIdentifierPath); + } + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + } + + /// <summary> + /// Starts the provider. + /// </summary> + internal void StartProvider() { + Contract.Ensures(this.IsRunning); + this.httpHost = HttpHost.CreateHost(this.RequestHandler); + } + + /// <summary> + /// Stops the provider. + /// </summary> + internal void StopProvider() { + Contract.Ensures(!this.IsRunning); + if (this.httpHost != null) { + this.httpHost.Dispose(); + this.httpHost = null; + } + } + + #region IDisposable Members + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + var host = this.httpHost as IDisposable; + if (host != null) { + host.Dispose(); + } + + this.httpHost = null; + } + } + + #endregion + + /// <summary> + /// Generates HTML for an identity page. + /// </summary> + /// <param name="providerEndpoint">The provider endpoint.</param> + /// <param name="localId">The local id.</param> + /// <returns>The HTML document to return to the RP.</returns> + private static string GenerateHtmlDiscoveryDocument(Uri providerEndpoint, string localId) { + Contract.Requires(providerEndpoint != null); + + const string DelegatedHtmlDiscoveryFormat = @"<html><head> + <link rel=""openid.server"" href=""{0}"" /> + <link rel=""openid.delegate"" href=""{1}"" /> + <link rel=""openid2.provider"" href=""{0}"" /> + <link rel=""openid2.local_id"" href=""{1}"" /> + </head><body></body></html>"; + + const string NonDelegatedHtmlDiscoveryFormat = @"<html><head> + <link rel=""openid.server"" href=""{0}"" /> + <link rel=""openid2.provider"" href=""{0}"" /> + </head><body></body></html>"; + + return string.Format( + localId != null ? DelegatedHtmlDiscoveryFormat : NonDelegatedHtmlDiscoveryFormat, + providerEndpoint.AbsoluteUri, + localId); + } + + /// <summary> + /// Generates the OP Identifier XRDS document. + /// </summary> + /// <param name="providerEndpoint">The provider endpoint.</param> + /// <param name="supportedExtensions">The supported extensions.</param> + /// <returns>The content of the XRDS document.</returns> + private static string GenerateXrdsOPIdentifierDocument(Uri providerEndpoint, IEnumerable<string> supportedExtensions) { + Contract.Requires(providerEndpoint != null); + Contract.Requires(supportedExtensions != null); + + const string OPIdentifierDiscoveryFormat = @"<xrds:XRDS + xmlns:xrds='xri://$xrds' + xmlns:openid='http://openid.net/xmlns/1.0' + xmlns='xri://$xrd*($v*2.0)'> + <XRD> + <Service priority='10'> + <Type>http://specs.openid.net/auth/2.0/server</Type> + {1} + <URI>{0}</URI> + </Service> + </XRD> +</xrds:XRDS>"; + + string extensions = string.Join( + "\n\t\t\t", + supportedExtensions.Select(ext => "<Type>" + ext + "</Type>").ToArray()); + return string.Format( + OPIdentifierDiscoveryFormat, + providerEndpoint.AbsoluteUri, + extensions); + } + + /// <summary> + /// Handles incoming HTTP requests. + /// </summary> + /// <param name="context">The HttpListener context.</param> + private void RequestHandler(HttpListenerContext context) { + Contract.Requires(context != null); + Contract.Requires(context.Response.OutputStream != null); + Contract.Requires(this.ProcessRequest != null); + Stream outputStream = context.Response.OutputStream; + Contract.Assume(outputStream != null); // CC static verification shortcoming. + + UriBuilder providerEndpointBuilder = new UriBuilder(); + providerEndpointBuilder.Scheme = Uri.UriSchemeHttp; + providerEndpointBuilder.Host = "localhost"; + providerEndpointBuilder.Port = context.Request.Url.Port; + providerEndpointBuilder.Path = ProviderPath; + Uri providerEndpoint = providerEndpointBuilder.Uri; + + if (context.Request.Url.AbsolutePath == ProviderPath) { + HttpRequestInfo requestInfo = new HttpRequestInfo(context.Request); + this.ProcessRequest(requestInfo, context.Response); + } else if (context.Request.Url.AbsolutePath.StartsWith(UserIdentifierPath, StringComparison.Ordinal)) { + using (StreamWriter sw = new StreamWriter(outputStream)) { + context.Response.StatusCode = (int)HttpStatusCode.OK; + + string localId = null; // string.Format("http://localhost:{0}/user", context.Request.Url.Port); + string html = GenerateHtmlDiscoveryDocument(providerEndpoint, localId); + sw.WriteLine(html); + } + context.Response.OutputStream.Close(); + } else if (context.Request.Url == this.OPIdentifier) { + context.Response.ContentType = "application/xrds+xml"; + context.Response.StatusCode = (int)HttpStatusCode.OK; + App.Logger.Info("Discovery on OP Identifier detected."); + using (StreamWriter sw = new StreamWriter(outputStream)) { + sw.Write(GenerateXrdsOPIdentifierDocument(providerEndpoint, Enumerable.Empty<string>())); + } + context.Response.OutputStream.Close(); + } else { + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + context.Response.OutputStream.Close(); + } + } + } +} diff --git a/samples/OpenIdOfflineProvider/HttpHost.cs b/samples/OpenIdOfflineProvider/HttpHost.cs new file mode 100644 index 0000000..390275a --- /dev/null +++ b/samples/OpenIdOfflineProvider/HttpHost.cs @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------- +// <copyright file="HttpHost.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Net; + using System.Threading; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// An HTTP Listener that dispatches incoming requests for handling. + /// </summary> + internal class HttpHost : IDisposable { + /// <summary> + /// The HttpListener that waits for incoming requests. + /// </summary> + private readonly HttpListener listener; + + /// <summary> + /// The thread that listens for incoming HTTP requests and dispatches them + /// to the <see cref="handler"/>. + /// </summary> + private Thread listenerThread; + + /// <summary> + /// The handler for incoming HTTP requests. + /// </summary> + private RequestHandler handler; + + /// <summary> + /// Initializes a new instance of the <see cref="HttpHost"/> class. + /// </summary> + /// <param name="handler">The handler for incoming HTTP requests.</param> + private HttpHost(RequestHandler handler) { + Contract.Requires(handler != null); + + this.Port = 45235; + this.handler = handler; + Random r = new Random(); + tryAgain: + try { + this.listener = new HttpListener(); + this.listener.Prefixes.Add(string.Format(CultureInfo.InvariantCulture, "http://localhost:{0}/", this.Port)); + this.listener.Start(); + } catch (HttpListenerException ex) { + if (ex.Message.Contains("conflicts")) { + this.Port += r.Next(1, 20); + goto tryAgain; + } + throw; + } + + this.listenerThread = new Thread(this.ProcessRequests); + this.listenerThread.Start(); + } + + /// <summary> + /// The request handler delegate. + /// </summary> + /// <param name="context">Information on the incoming HTTP request.</param> + internal delegate void RequestHandler(HttpListenerContext context); + + /// <summary> + /// Gets the port that HTTP requests are being listened for on. + /// </summary> + public int Port { get; private set; } + + /// <summary> + /// Gets the base URI for all incoming web requests that will be received. + /// </summary> + public Uri BaseUri { + get { return new Uri("http://localhost:" + this.Port.ToString() + "/"); } + } + + /// <summary> + /// Creates the HTTP host. + /// </summary> + /// <param name="handler">The handler for incoming HTTP requests.</param> + /// <returns>The instantiated host.</returns> + public static HttpHost CreateHost(RequestHandler handler) { + Contract.Requires(handler != null); + Contract.Ensures(Contract.Result<HttpHost>() != null); + + return new HttpHost(handler); + } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + this.listener.Close(); + this.listenerThread.Join(1000); + this.listenerThread.Abort(); + } + } + + #endregion + + /// <summary> + /// The HTTP listener thread body. + /// </summary> + private void ProcessRequests() { + Contract.Requires(this.listener != null); + + try { + while (true) { + HttpListenerContext context = this.listener.GetContext(); + this.handler(context); + } + } catch (HttpListenerException ex) { + // the listener is probably being shut down + App.Logger.Warn("HTTP listener is closing down.", ex); + } + } + } +} diff --git a/samples/OpenIdOfflineProvider/MainWindow.xaml b/samples/OpenIdOfflineProvider/MainWindow.xaml new file mode 100644 index 0000000..de215ba --- /dev/null +++ b/samples/OpenIdOfflineProvider/MainWindow.xaml @@ -0,0 +1,25 @@ +<Window x:Class="DotNetOpenAuth.OpenIdOfflineProvider.MainWindow" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + Title="DotNetOpenAuth Offline OpenID Provider" Height="289" Width="493"> + <Grid Margin="4"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*"/> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Label Grid.Row="0">OP Identifier</Label> + <Label Grid.Column="1" Grid.Row="0" Name="opIdentifierLabel" ToolTip="Click to copy URI to clipboard" MouseDown="opIdentifierLabel_MouseDown" /> + <Label Grid.Row="1">checkid requests</Label> + <ComboBox Grid.Column="1" Grid.Row="1" Name="checkidRequestList" SelectedIndex="0"> + <ComboBoxItem>Auto respond: Yes</ComboBoxItem> + <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" /> + </Grid> +</Window> diff --git a/samples/OpenIdOfflineProvider/MainWindow.xaml.cs b/samples/OpenIdOfflineProvider/MainWindow.xaml.cs new file mode 100644 index 0000000..f4f88ca --- /dev/null +++ b/samples/OpenIdOfflineProvider/MainWindow.xaml.cs @@ -0,0 +1,168 @@ +//----------------------------------------------------------------------- +// <copyright file="MainWindow.xaml.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using System.Windows; + using System.Windows.Controls; + using System.Windows.Data; + using System.Windows.Documents; + using System.Windows.Input; + using System.Windows.Media; + using System.Windows.Media.Imaging; + using System.Windows.Navigation; + using System.Windows.Shapes; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + using log4net; + using log4net.Appender; + using log4net.Core; + + /// <summary> + /// Interaction logic for MainWindow.xaml + /// </summary> + public partial class MainWindow : Window, IDisposable { + /// <summary> + /// The OpenID Provider host object. + /// </summary> + private HostedProvider hostedProvider = new HostedProvider(); + + /// <summary> + /// The logger the application may use. + /// </summary> + private ILog logger = log4net.LogManager.GetLogger(typeof(MainWindow)); + + /// <summary> + /// Initializes a new instance of the <see cref="MainWindow"/> class. + /// </summary> + public MainWindow() { + this.InitializeComponent(); + this.hostedProvider.ProcessRequest = this.ProcessRequest; + TextWriterAppender boxLogger = log4net.LogManager.GetRepository().GetAppenders().OfType<TextWriterAppender>().FirstOrDefault(a => a.Name == "TextBoxAppender"); + if (boxLogger != null) { + boxLogger.Writer = new TextBoxTextWriter(logBox); + } + + this.startProvider(); + } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + var host = this.hostedProvider as IDisposable; + if (host != null) { + host.Dispose(); + } + + this.hostedProvider = null; + } + } + + #endregion + + /// <summary> + /// Raises the <see cref="E:Closing"/> event. + /// </summary> + /// <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param> + protected override void OnClosing(System.ComponentModel.CancelEventArgs e) { + this.stopProvider(); + base.OnClosing(e); + } + + /// <summary> + /// Processes an incoming request at the OpenID Provider endpoint. + /// </summary> + /// <param name="requestInfo">The request info.</param> + /// <param name="response">The response.</param> + private void ProcessRequest(HttpRequestInfo requestInfo, HttpListenerResponse response) { + IRequest request = this.hostedProvider.Provider.GetRequest(requestInfo); + if (request == null) { + App.Logger.Error("A request came in that did not carry an OpenID message."); + response.ContentType = "text/html"; + response.StatusCode = (int)HttpStatusCode.BadRequest; + using (StreamWriter sw = new StreamWriter(response.OutputStream)) { + sw.WriteLine("<html><body>This is an OpenID Provider endpoint.</body></html>"); + } + return; + } + + this.Dispatcher.Invoke((Action)delegate { + if (!request.IsResponseReady) { + var authRequest = request as IAuthenticationRequest; + if (authRequest != null) { + switch (checkidRequestList.SelectedIndex) { + case 0: + if (authRequest.IsDirectedIdentity) { + authRequest.ClaimedIdentifier = new Uri(this.hostedProvider.UserIdentityPageBase, "directedidentity"); + authRequest.LocalIdentifier = authRequest.ClaimedIdentifier; + } + authRequest.IsAuthenticated = true; + break; + case 1: + authRequest.IsAuthenticated = false; + break; + case 2: + IntPtr oldForegroundWindow = NativeMethods.GetForegroundWindow(); + bool stoleFocus = NativeMethods.SetForegroundWindow(this); + CheckIdWindow.ProcessAuthentication(this.hostedProvider, authRequest); + if (stoleFocus) { + NativeMethods.SetForegroundWindow(oldForegroundWindow); + } + break; + } + } + } + }); + + this.hostedProvider.Provider.PrepareResponse(request).Send(response); + } + + /// <summary> + /// Starts the provider. + /// </summary> + private void startProvider() { + this.hostedProvider.StartProvider(); + this.opIdentifierLabel.Content = this.hostedProvider.OPIdentifier; + } + + /// <summary> + /// Stops the provider. + /// </summary> + private void stopProvider() { + this.hostedProvider.StopProvider(); + this.opIdentifierLabel.Content = string.Empty; + } + + /// <summary> + /// Handles the MouseDown event of the opIdentifierLabel control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param> + private void opIdentifierLabel_MouseDown(object sender, MouseButtonEventArgs e) { + Clipboard.SetText(opIdentifierLabel.Content.ToString()); + } + } +} diff --git a/samples/OpenIdOfflineProvider/NativeMethods.cs b/samples/OpenIdOfflineProvider/NativeMethods.cs new file mode 100644 index 0000000..b512d70 --- /dev/null +++ b/samples/OpenIdOfflineProvider/NativeMethods.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// <copyright file="NativeMethods.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Runtime.InteropServices; + using System.Windows; + using System.Windows.Interop; + + /// <summary> + /// P/Invoke methods and wrappers. + /// </summary> + internal static class NativeMethods { + /// <summary> + /// Gets the HWND of the current foreground window on the system. + /// </summary> + /// <returns>A handle to the foreground window.</returns> + [DllImport("user32.dll")] + internal static extern IntPtr GetForegroundWindow(); + + /// <summary> + /// Sets the foreground window of the system. + /// </summary> + /// <param name="hWnd">The HWND of the window to set as active.</param> + /// <returns>A value indicating whether the foreground window was actually changed.</returns> + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetForegroundWindow(IntPtr hWnd); + + /// <summary> + /// Sets the foreground window of the system. + /// </summary> + /// <param name="window">The window to bring to the foreground.</param> + /// <returns> + /// A value indicating whether the foreground window was actually changed. + /// </returns> + internal static bool SetForegroundWindow(Window window) { + return SetForegroundWindow(new WindowInteropHelper(window).Handle); + } + + /// <summary> + /// Sets the active window of the process. + /// </summary> + /// <param name="window">The window to bring to the foreground.</param> + internal static void SetActiveWindow(Window window) { + SetActiveWindow(new WindowInteropHelper(window).Handle); + } + + /// <summary> + /// Sets the active window of the process. + /// </summary> + /// <param name="hWnd">The HWND of the window to set as active.</param> + /// <returns>The window that was previously active?</returns> + [DllImport("user32.dll")] + private static extern IntPtr SetActiveWindow(IntPtr hWnd); + } +} diff --git a/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj b/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj new file mode 100644 index 0000000..1bb2367 --- /dev/null +++ b/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj @@ -0,0 +1,171 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion>9.0.30729</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{5C65603B-235F-47E6-B536-06385C60DE7F}</ProjectGuid> + <OutputType>WinExe</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth.OpenIdOfflineProvider</RootNamespace> + <AssemblyName>OpenIdOfflineProvider</AssemblyName> + <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <WarningLevel>4</WarningLevel> + <UICulture>en-US</UICulture> + <ApplicationIcon>openid.ico</ApplicationIcon> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <CodeContractsEnableRuntimeChecking>False</CodeContractsEnableRuntimeChecking> + <CodeContractsCustomRewriterAssembly> + </CodeContractsCustomRewriterAssembly> + <CodeContractsCustomRewriterClass> + </CodeContractsCustomRewriterClass> + <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel> + <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis> + <CodeContractsBuildReferenceAssembly>False</CodeContractsBuildReferenceAssembly> + <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> + <CodeContractsBoundsObligations>False</CodeContractsBoundsObligations> + <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> + <CodeContractsLibPaths> + </CodeContractsLibPaths> + <CodeContractsPlatformPath> + </CodeContractsPlatformPath> + <CodeContractsExtraAnalysisOptions> + </CodeContractsExtraAnalysisOptions> + <CodeContractsBaseLineFile> + </CodeContractsBaseLineFile> + <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> + <CodeContractsRunInBackground>True</CodeContractsRunInBackground> + <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies> + <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Sign)' == 'true' "> + <SignAssembly>true</SignAssembly> + <AssemblyOriginatorKeyFile>..\..\src\official-build-key.pfx</AssemblyOriginatorKeyFile> + <DefineConstants>$(DefineConstants);StrongNameSigned</DefineConstants> + </PropertyGroup> + <ItemGroup> + <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\..\lib\log4net.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Contracts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=736440c9b414ea16, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\..\lib\Microsoft.Contracts.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Web" /> + <Reference Include="System.Xml.Linq"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Data.DataSetExtensions"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + <Reference Include="UIAutomationProvider"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + <Reference Include="WindowsBase"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + <Reference Include="PresentationCore"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + <Reference Include="PresentationFramework"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + </ItemGroup> + <ItemGroup> + <ApplicationDefinition Include="App.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </ApplicationDefinition> + <Page Include="CheckIdWindow.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="MainWindow.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Compile Include="App.xaml.cs"> + <DependentUpon>App.xaml</DependentUpon> + <SubType>Code</SubType> + </Compile> + <Compile Include="MainWindow.xaml.cs"> + <DependentUpon>MainWindow.xaml</DependentUpon> + <SubType>Code</SubType> + </Compile> + </ItemGroup> + <ItemGroup> + <Compile Include="CheckIdWindow.xaml.cs"> + <DependentUpon>CheckIdWindow.xaml</DependentUpon> + </Compile> + <Compile Include="HostedProvider.cs" /> + <Compile Include="HttpHost.cs" /> + <Compile Include="NativeMethods.cs" /> + <Compile Include="Properties\AssemblyInfo.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="Properties\Resources.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>Resources.resx</DependentUpon> + </Compile> + <Compile Include="Properties\Settings.Designer.cs"> + <AutoGen>True</AutoGen> + <DependentUpon>Settings.settings</DependentUpon> + <DesignTimeSharedInput>True</DesignTimeSharedInput> + </Compile> + <Compile Include="TextBoxTextWriter.cs" /> + <EmbeddedResource Include="Properties\Resources.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + </EmbeddedResource> + <None Include="App.config" /> + <None Include="Properties\Settings.settings"> + <Generator>SettingsSingleFileGenerator</Generator> + <LastGenOutput>Settings.Designer.cs</LastGenOutput> + </None> + <AppDesigner Include="Properties\" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> + <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> + <Name>DotNetOpenAuth</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Resource Include="openid.ico" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/samples/OpenIdOfflineProvider/Properties/AssemblyInfo.cs b/samples/OpenIdOfflineProvider/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..adaded3 --- /dev/null +++ b/samples/OpenIdOfflineProvider/Properties/AssemblyInfo.cs @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OpenIdOfflineProvider")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Andrew Arnott")] +[assembly: AssemblyProduct("DotNetOpenAuth Offline OpenID Provider")] +[assembly: AssemblyCopyright("Copyright © Andrew Arnott 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located + // (used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly)] // where the generic resource dictionary is located + // (used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/OpenIdOfflineProvider/Properties/Resources.Designer.cs b/samples/OpenIdOfflineProvider/Properties/Resources.Designer.cs new file mode 100644 index 0000000..ac28082 --- /dev/null +++ b/samples/OpenIdOfflineProvider/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4912 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth.OpenIdOfflineProvider.Properties { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.OpenIdOfflineProvider.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/samples/OpenIdOfflineProvider/Properties/Resources.resx b/samples/OpenIdOfflineProvider/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/samples/OpenIdOfflineProvider/Properties/Resources.resx @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> +</root>
\ No newline at end of file diff --git a/samples/OpenIdOfflineProvider/Properties/Settings.Designer.cs b/samples/OpenIdOfflineProvider/Properties/Settings.Designer.cs new file mode 100644 index 0000000..014a460 --- /dev/null +++ b/samples/OpenIdOfflineProvider/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4912 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth.OpenIdOfflineProvider.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/samples/OpenIdOfflineProvider/Properties/Settings.settings b/samples/OpenIdOfflineProvider/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/samples/OpenIdOfflineProvider/Properties/Settings.settings @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8'?> +<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)"> + <Profiles> + <Profile Name="(Default)" /> + </Profiles> + <Settings /> +</SettingsFile>
\ No newline at end of file diff --git a/samples/OpenIdOfflineProvider/Settings.StyleCop b/samples/OpenIdOfflineProvider/Settings.StyleCop new file mode 100644 index 0000000..0d69b34 --- /dev/null +++ b/samples/OpenIdOfflineProvider/Settings.StyleCop @@ -0,0 +1,19 @@ +<StyleCopSettings Version="4.3"> + <Analyzers> + <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.DocumentationRules"> + <Rules> + <Rule Name="FileMustHaveHeader"> + <RuleSettings> + <BooleanProperty Name="Enabled">True</BooleanProperty> + </RuleSettings> + </Rule> + <Rule Name="ElementsMustBeDocumented"> + <RuleSettings> + <BooleanProperty Name="Enabled">True</BooleanProperty> + </RuleSettings> + </Rule> + </Rules> + <AnalyzerSettings /> + </Analyzer> + </Analyzers> +</StyleCopSettings>
\ No newline at end of file diff --git a/samples/OpenIdOfflineProvider/TextBoxTextWriter.cs b/samples/OpenIdOfflineProvider/TextBoxTextWriter.cs new file mode 100644 index 0000000..8118986 --- /dev/null +++ b/samples/OpenIdOfflineProvider/TextBoxTextWriter.cs @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------- +// <copyright file="TextBoxTextWriter.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Diagnostics.Contracts; + using System.IO; + using System.Text; + using System.Windows.Controls; + + /// <summary> + /// A text writer that appends all write calls to a text box. + /// </summary> + internal class TextBoxTextWriter : TextWriter { + /// <summary> + /// Initializes a new instance of the <see cref="TextBoxTextWriter"/> class. + /// </summary> + /// <param name="box">The text box to append log messages to.</param> + internal TextBoxTextWriter(TextBox box) { + Contract.Requires(box != null); + this.Box = box; + } + + /// <summary> + /// Gets the <see cref="T:System.Text.Encoding"/> in which the output is written. + /// </summary> + /// <returns> + /// The Encoding in which the output is written. + /// </returns> + public override Encoding Encoding { + get { return Encoding.Unicode; } + } + + /// <summary> + /// Gets the box to append to. + /// </summary> + internal TextBox Box { get; private set; } + + /// <summary> + /// Writes a character to the text stream. + /// </summary> + /// <param name="value">The character to write to the text stream.</param> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="T:System.IO.TextWriter"/> is closed. + /// </exception> + /// <exception cref="T:System.IO.IOException"> + /// An I/O error occurs. + /// </exception> + public override void Write(char value) { + this.Box.Dispatcher.BeginInvoke((Action<string>)this.AppendText, value.ToString()); + } + + /// <summary> + /// Writes a string to the text stream. + /// </summary> + /// <param name="value">The string to write.</param> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="T:System.IO.TextWriter"/> is closed. + /// </exception> + /// <exception cref="T:System.IO.IOException"> + /// An I/O error occurs. + /// </exception> + public override void Write(string value) { + this.Box.Dispatcher.BeginInvoke((Action<string>)this.AppendText, value); + } + + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [ContractInvariantMethod] + protected void ObjectInvariant() { + Contract.Invariant(this.Box != null); + } + + /// <summary> + /// Appends text to the text box. + /// </summary> + /// <param name="value">The string to append.</param> + private void AppendText(string value) { + this.Box.AppendText(value); + this.Box.ScrollToEnd(); + } + } +} diff --git a/samples/OpenIdOfflineProvider/openid.ico b/samples/OpenIdOfflineProvider/openid.ico Binary files differnew file mode 100644 index 0000000..651aeba --- /dev/null +++ b/samples/OpenIdOfflineProvider/openid.ico diff --git a/samples/OpenIdProviderMvc/App_Data/Users.xml b/samples/OpenIdProviderMvc/App_Data/Users.xml index cffe009..7f70bc6 100644 --- a/samples/OpenIdProviderMvc/App_Data/Users.xml +++ b/samples/OpenIdProviderMvc/App_Data/Users.xml @@ -3,21 +3,26 @@ <User> <UserName>bob</UserName> <Password>test</Password> + <Salt>CDI=</Salt> </User> <User> <UserName>bob1</UserName> <Password>test</Password> + <Salt>CAI=</Salt> </User> <User> <UserName>bob2</UserName> <Password>test</Password> + <Salt>hYMC</Salt> </User> <User> <UserName>bob3</UserName> <Password>test</Password> + <Salt>hTKDAg==</Salt> </User> <User> <UserName>bob4</UserName> <Password>test</Password> + <Salt>hTkDAg==</Salt> </User> </Users> diff --git a/samples/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs b/samples/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs new file mode 100644 index 0000000..2b9e01c --- /dev/null +++ b/samples/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs @@ -0,0 +1,23 @@ +namespace OpenIdProviderMvc.Code { + using System; + using System.Web.Security; + using DotNetOpenAuth.ApplicationBlock.Provider; + using DotNetOpenAuth.OpenId; + using OpenIdProviderMvc.Models; + + internal class AnonymousIdentifierProvider : AnonymousIdentifierProviderBase { + internal AnonymousIdentifierProvider() + : base(Util.GetAppPathRootedUri("anon?id=")) { + } + + protected override byte[] GetHashSaltForLocalIdentifier(Identifier localIdentifier) { + // This is just a sample with no database... a real web app MUST return + // a reasonable salt here and have that salt be persistent for each user. + var membership = (ReadOnlyXmlMembershipProvider)Membership.Provider; + string username = User.GetUserFromClaimedIdentifier(new Uri(localIdentifier)); + string salt = membership.GetSalt(username); + return Convert.FromBase64String(salt); + ////return AnonymousIdentifierProviderBase.GetNewSalt(5); + } + } +} diff --git a/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs b/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs index 3da0f8e..cc5a321 100644 --- a/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs +++ b/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs @@ -236,6 +236,11 @@ throw new NotSupportedException(); } + internal string GetSalt(string userName) { + this.ReadMembershipDataStore(); + return this.users[userName].Email; + } + // Helper method private void ReadMembershipDataStore() { lock (this) { @@ -246,11 +251,13 @@ XmlNodeList nodes = doc.GetElementsByTagName("User"); foreach (XmlNode node in nodes) { + // Yes, we're misusing some of these fields. A real app would + // have the right fields from a database to use. MembershipUser user = new MembershipUser( Name, // Provider name node["UserName"].InnerText, // Username null, // providerUserKey - null, // Email + node["Salt"].InnerText, // Email string.Empty, // passwordQuestion node["Password"].InnerText, // Comment true, // isApproved diff --git a/samples/OpenIdProviderMvc/Code/Util.cs b/samples/OpenIdProviderMvc/Code/Util.cs new file mode 100644 index 0000000..6623952 --- /dev/null +++ b/samples/OpenIdProviderMvc/Code/Util.cs @@ -0,0 +1,17 @@ +namespace OpenIdProviderMvc.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + + internal static class Util { + internal static Uri GetAppPathRootedUri(string value) { + string appPath = HttpContext.Current.Request.ApplicationPath.ToLowerInvariant(); + if (!appPath.EndsWith("/")) { + appPath += "/"; + } + + return new Uri(HttpContext.Current.Request.Url, appPath + value); + } + } +} diff --git a/samples/OpenIdProviderMvc/Controllers/HomeController.cs b/samples/OpenIdProviderMvc/Controllers/HomeController.cs index 5ba08b3..346e838 100644 --- a/samples/OpenIdProviderMvc/Controllers/HomeController.cs +++ b/samples/OpenIdProviderMvc/Controllers/HomeController.cs @@ -23,5 +23,9 @@ public ActionResult Xrds() { return View(); } + + public ActionResult PpidXrds() { + return View(); + } } } diff --git a/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs b/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs index f75377c..e353268 100644 --- a/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs +++ b/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs @@ -5,8 +5,11 @@ namespace OpenIdProviderMvc.Controllers { using System.Web; using System.Web.Mvc; using System.Web.Mvc.Ajax; + using DotNetOpenAuth.ApplicationBlock.Provider; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Provider; + using OpenIdProviderMvc.Code; public class OpenIdController : Controller { internal static OpenIdProvider OpenIdProvider = new OpenIdProvider(); @@ -16,16 +19,68 @@ namespace OpenIdProviderMvc.Controllers { set { ProviderEndpoint.PendingAuthenticationRequest = value; } } + [ValidateInput(false)] + public ActionResult PpidProvider() { + return this.DoProvider(true); + } + + [ValidateInput(false)] public ActionResult Provider() { + return this.DoProvider(false); + } + + [Authorize] + public ActionResult SendAssertion(bool pseudonymous) { + IAuthenticationRequest authReq = PendingAuthenticationRequest; + PendingAuthenticationRequest = null; + if (authReq == null) { + throw new InvalidOperationException(); + } + + Identifier localIdentifier = Models.User.GetClaimedIdentifierForUser(User.Identity.Name); + + if (pseudonymous) { + if (!authReq.IsDirectedIdentity) { + throw new InvalidOperationException("Directed identity is the only supported scenario for anonymous identifiers."); + } + + var anonProvider = new AnonymousIdentifierProvider(); + authReq.ScrubPersonallyIdentifiableInformation(localIdentifier, anonProvider, true); + authReq.IsAuthenticated = true; + } else { + if (authReq.IsDirectedIdentity) { + authReq.LocalIdentifier = localIdentifier; + authReq.ClaimedIdentifier = localIdentifier; + authReq.IsAuthenticated = true; + } else { + if (authReq.LocalIdentifier == localIdentifier) { + authReq.IsAuthenticated = true; + if (!authReq.IsDelegatedIdentifier) { + authReq.ClaimedIdentifier = authReq.LocalIdentifier; + } + } else { + authReq.IsAuthenticated = false; + } + } + + // TODO: Respond to AX/sreg extension requests here. + // We don't want to add these extension responses for anonymous identifiers + // because they could leak information about the user's identity. + } + + return OpenIdProvider.PrepareResponse(authReq).AsActionResult(); + } + + private ActionResult DoProvider(bool pseudonymous) { IRequest request = OpenIdProvider.GetRequest(); if (request != null) { var authRequest = request as IAuthenticationRequest; if (authRequest != null) { PendingAuthenticationRequest = authRequest; if (User.Identity.IsAuthenticated && (authRequest.IsDirectedIdentity || Models.User.GetClaimedIdentifierForUser(User.Identity.Name) == authRequest.LocalIdentifier)) { - return this.SendAssertion(); + return this.SendAssertion(pseudonymous); } else { - return RedirectToAction("LogOn", "Account", new { returnUrl = Url.Action("SendAssertion") }); + return RedirectToAction("LogOn", "Account", new { returnUrl = Url.Action("SendAssertion", new { pseudonymous = pseudonymous }) }); } } @@ -38,30 +93,5 @@ namespace OpenIdProviderMvc.Controllers { return View(); } } - - [Authorize] - public ActionResult SendAssertion() { - IAuthenticationRequest authReq = PendingAuthenticationRequest; - PendingAuthenticationRequest = null; - if (authReq == null) { - throw new InvalidOperationException(); - } - - if (authReq.IsDirectedIdentity) { - authReq.LocalIdentifier = Models.User.GetClaimedIdentifierForUser(User.Identity.Name); - authReq.ClaimedIdentifier = authReq.LocalIdentifier; - authReq.IsAuthenticated = true; - } else { - if (authReq.LocalIdentifier == Models.User.GetClaimedIdentifierForUser(User.Identity.Name)) { - authReq.IsAuthenticated = true; - if (!authReq.IsDelegatedIdentifier) { - authReq.ClaimedIdentifier = authReq.LocalIdentifier; - } - } else { - authReq.IsAuthenticated = false; - } - } - return OpenIdProvider.PrepareResponse(authReq).AsActionResult(); - } } } diff --git a/samples/OpenIdProviderMvc/Controllers/UserController.cs b/samples/OpenIdProviderMvc/Controllers/UserController.cs index 5d46388..8b3f944 100644 --- a/samples/OpenIdProviderMvc/Controllers/UserController.cs +++ b/samples/OpenIdProviderMvc/Controllers/UserController.cs @@ -7,6 +7,14 @@ namespace OpenIdProviderMvc.Controllers { using System.Web.Mvc.Ajax; public class UserController : Controller { + public ActionResult PpidIdentity() { + if (Request.AcceptTypes.Contains("application/xrds+xml")) { + return View("PpidXrds"); + } + + return View(); + } + public ActionResult Identity(string id) { var redirect = this.RedirectIfNotNormalizedRequestUri(); if (redirect != null) { @@ -25,6 +33,10 @@ namespace OpenIdProviderMvc.Controllers { return View(); } + public ActionResult PpidXrds() { + return View(); + } + private ActionResult RedirectIfNotNormalizedRequestUri() { Uri normalized = Models.User.GetNormalizedClaimedIdentifier(Request.Url); if (Request.Url != normalized) { diff --git a/samples/OpenIdProviderMvc/Global.asax.cs b/samples/OpenIdProviderMvc/Global.asax.cs index b0d1b60..8c57961 100644 --- a/samples/OpenIdProviderMvc/Global.asax.cs +++ b/samples/OpenIdProviderMvc/Global.asax.cs @@ -22,6 +22,14 @@ "user/{id}/{action}", new { controller = "User", action = "Identity", id = string.Empty }); routes.MapRoute( + "PPID identifiers", + "anon", + new { controller = "User", action = "PpidIdentity", id = string.Empty }); + routes.MapRoute( + "PpidXrds", + "PpidXrds", + new { controller = "Home", action = "PpidXrds" }); // Parameter defaults + routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = string.Empty }); // Parameter defaults diff --git a/samples/OpenIdProviderMvc/Models/User.cs b/samples/OpenIdProviderMvc/Models/User.cs index 577aa05..443c004 100644 --- a/samples/OpenIdProviderMvc/Models/User.cs +++ b/samples/OpenIdProviderMvc/Models/User.cs @@ -5,17 +5,23 @@ using System.Text.RegularExpressions; using System.Web; using System.Web.Routing; + using OpenIdProviderMvc.Code; internal class User { + internal static Uri PpidClaimedIdentifierBaseUri { + get { return Util.GetAppPathRootedUri("anon?id="); } + } + + internal static Uri ClaimedIdentifierBaseUri { + get { return Util.GetAppPathRootedUri("user/"); } + } + internal static Uri GetClaimedIdentifierForUser(string username) { - string appPath = HttpContext.Current.Request.ApplicationPath; - if (!appPath.EndsWith("/")) { - appPath += "/"; + if (String.IsNullOrEmpty(username)) { + throw new ArgumentNullException("username"); } - Uri claimedIdentifier = new Uri( - HttpContext.Current.Request.Url, - appPath + "user/" + username); - return new Uri(claimedIdentifier.AbsoluteUri.ToLowerInvariant()); + + return new Uri(ClaimedIdentifierBaseUri, username.ToLowerInvariant()); } internal static string GetUserFromClaimedIdentifier(Uri claimedIdentifier) { diff --git a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj index 80e8e64..5caf26d 100644 --- a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj +++ b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj @@ -65,10 +65,12 @@ </ItemGroup> <ItemGroup> <Compile Include="Code\AccountMembershipService.cs" /> + <Compile Include="Code\AnonymousIdentifierProvider.cs" /> <Compile Include="Code\FormsAuthenticationService.cs" /> <Compile Include="Code\IFormsAuthentication.cs" /> <Compile Include="Code\IMembershipService.cs" /> <Compile Include="Code\ReadOnlyXmlMembershipProvider.cs" /> + <Compile Include="Code\Util.cs" /> <Compile Include="Controllers\AccountController.cs" /> <Compile Include="Controllers\HomeController.cs" /> <Compile Include="Controllers\OpenIdController.cs" /> @@ -90,8 +92,11 @@ <Content Include="Views\Account\ChangePassword.aspx" /> <Content Include="Views\Account\ChangePasswordSuccess.aspx" /> <Content Include="Views\Account\Register.aspx" /> + <Content Include="Views\Home\PpidXrds.aspx" /> <Content Include="Views\Home\Xrds.aspx" /> <Content Include="Views\OpenId\Provider.aspx" /> + <Content Include="Views\User\PpidXrds.aspx" /> + <Content Include="Views\User\PpidIdentity.aspx" /> <Content Include="Views\User\Identity.aspx" /> <Content Include="Views\User\Xrds.aspx" /> <Content Include="Web.config" /> @@ -117,6 +122,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="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" /> diff --git a/samples/OpenIdProviderMvc/Views/Home/PpidXrds.aspx b/samples/OpenIdProviderMvc/Views/Home/PpidXrds.aspx new file mode 100644 index 0000000..990a3df --- /dev/null +++ b/samples/OpenIdProviderMvc/Views/Home/PpidXrds.aspx @@ -0,0 +1,18 @@ +<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> +<%-- +This page is a required as part of the service discovery phase of the openid +protocol (step 1). It simply renders the xml for doing service discovery of +server.aspx using the xrds mechanism. +This XRDS doc is discovered via the user.aspx page. +--%> +<xrds:XRDS + xmlns:xrds="xri://$xrds" + xmlns:openid="http://openid.net/xmlns/1.0" + xmlns="xri://$xrd*($v*2.0)"> + <XRD> + <Service priority="10"> + <Type>http://specs.openid.net/auth/2.0/server</Type> + <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/OpenId/PpidProvider"))%></URI> + </Service> + </XRD> +</xrds:XRDS> diff --git a/samples/OpenIdProviderMvc/Views/Shared/Site.Master b/samples/OpenIdProviderMvc/Views/Shared/Site.Master index 8df2d5f..073908e 100644 --- a/samples/OpenIdProviderMvc/Views/Shared/Site.Master +++ b/samples/OpenIdProviderMvc/Views/Shared/Site.Master @@ -13,7 +13,7 @@ <div class="page"> <div id="header"> <div id="title"> - <h1>My MVC Application</h1> + <h1>OpenID Provider MVC Application</h1> </div> <div id="logindisplay"> <% Html.RenderPartial("LogOnUserControl"); %> diff --git a/samples/OpenIdProviderMvc/Views/User/Identity.aspx b/samples/OpenIdProviderMvc/Views/User/Identity.aspx index 632df43..bb50899 100644 --- a/samples/OpenIdProviderMvc/Views/User/Identity.aspx +++ b/samples/OpenIdProviderMvc/Views/User/Identity.aspx @@ -3,7 +3,7 @@ <%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Provider" TagPrefix="op" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> - <%=ViewData["username"]%> + <%=Html.Encode(ViewData["username"])%> identity page </asp:Content> <asp:Content runat="server" ContentPlaceHolderID="HeadContent"> @@ -12,7 +12,7 @@ </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>This is - <%=ViewData["username"]%>'s OpenID identity page </h2> + <%=Html.Encode(ViewData["username"])%>'s OpenID identity page </h2> <% if (string.Equals(User.Identity.Name, ViewData["username"])) { %> <p>This is <b>your</b> identity page. </p> diff --git a/samples/OpenIdProviderMvc/Views/User/PpidIdentity.aspx b/samples/OpenIdProviderMvc/Views/User/PpidIdentity.aspx new file mode 100644 index 0000000..f33a694 --- /dev/null +++ b/samples/OpenIdProviderMvc/Views/User/PpidIdentity.aspx @@ -0,0 +1,16 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> + +<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Provider" + TagPrefix="op" %> +<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> + Identity page +</asp:Content> +<asp:Content runat="server" ContentPlaceHolderID="HeadContent"> + <op:IdentityEndpoint ID="IdentityEndpoint11" runat="server" ProviderEndpointUrl="~/OpenId/PpidProvider" + ProviderVersion="V11" /> + <op:IdentityEndpoint ID="IdentityEndpoint20" runat="server" ProviderEndpointUrl="~/OpenId/PpidProvider" + XrdsUrl="~/User/all/ppidxrds" XrdsAutoAnswer="false" /> +</asp:Content> +<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> + <h2>OpenID identity page </h2> +</asp:Content> diff --git a/samples/OpenIdProviderMvc/Views/User/PpidXrds.aspx b/samples/OpenIdProviderMvc/Views/User/PpidXrds.aspx new file mode 100644 index 0000000..67256bd --- /dev/null +++ b/samples/OpenIdProviderMvc/Views/User/PpidXrds.aspx @@ -0,0 +1,13 @@ +<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> +<XRDS xmlns="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0"> + <XRD xmlns="xri://$xrd*($v*2.0)"> + <Service priority="10"> + <Type>http://specs.openid.net/auth/2.0/signon</Type> + <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/OpenId/PpidProvider"))%></URI> + </Service> + <Service priority="20"> + <Type>http://openid.net/signon/1.0</Type> + <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/OpenId/PpidProvider"))%></URI> + </Service> + </XRD> +</XRDS> diff --git a/samples/OpenIdRelyingPartyMvc/Controllers/UserController.cs b/samples/OpenIdRelyingPartyMvc/Controllers/UserController.cs index c0478b1..fd22389 100644 --- a/samples/OpenIdRelyingPartyMvc/Controllers/UserController.cs +++ b/samples/OpenIdRelyingPartyMvc/Controllers/UserController.cs @@ -34,6 +34,7 @@ return View("Login"); } + [ValidateInput(false)] public ActionResult Authenticate(string returnUrl) { var response = openid.GetResponse(); if (response == null) { diff --git a/samples/OpenIdRelyingPartyMvc/Views/User/LoginPopup.aspx b/samples/OpenIdRelyingPartyMvc/Views/User/LoginPopup.aspx index 67006fa..e7bc18a 100644 --- a/samples/OpenIdRelyingPartyMvc/Views/User/LoginPopup.aspx +++ b/samples/OpenIdRelyingPartyMvc/Views/User/LoginPopup.aspx @@ -28,7 +28,7 @@ if (box.style.display != 'none') { box.focus(); } - }, + } }); $('#loggedOut').dialog({ @@ -38,15 +38,15 @@ resizable: false, closeOnEscape: true, buttons: { - "Ok": function() { $(this).dialog('close'); }, - }, + "Ok": function() { $(this).dialog('close'); } + } }); $('#loginAction').click(function() { $('#openidlogin').dialog('open'); return false; }); - + $('#logoutAction').click(function() { // TODO: asynchronously log out. document.setClaimedIdentifier(); @@ -120,7 +120,7 @@ box.focus(); } this.lastIdentifierTemplate = identifierTemplate; - } + }; $('#loginButton').click(function() { completeLogin(); diff --git a/samples/OpenIdRelyingPartyWebForms/Code/InMemoryTokenManager.cs b/samples/OpenIdRelyingPartyWebForms/Code/InMemoryTokenManager.cs new file mode 100644 index 0000000..e665cb6 --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/Code/InMemoryTokenManager.cs @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------- +// <copyright file="InMemoryTokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace OpenIdRelyingPartyWebForms.Code { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.OpenId.Extensions.OAuth; + + public class InMemoryTokenManager : IConsumerTokenManager, IOpenIdOAuthTokenManager { + private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>(); + + public InMemoryTokenManager(string consumerKey, string consumerSecret) { + if (String.IsNullOrEmpty(consumerKey)) { + throw new ArgumentNullException("consumerKey"); + } + + this.ConsumerKey = consumerKey; + this.ConsumerSecret = consumerSecret; + } + + public string ConsumerKey { get; private set; } + + public string ConsumerSecret { get; private set; } + + #region ITokenManager Members + + public string GetConsumerSecret(string consumerKey) { + if (consumerKey == this.ConsumerKey) { + return this.ConsumerSecret; + } else { + throw new ArgumentException("Unrecognized consumer key.", "consumerKey"); + } + } + + public string GetTokenSecret(string token) { + return this.tokensAndSecrets[token]; + } + + public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) { + this.tokensAndSecrets[response.Token] = response.TokenSecret; + } + + /// <summary> + /// Checks whether a given request token has already been authorized + /// by some user for use by the Consumer that requested it. + /// </summary> + /// <param name="requestToken">The Consumer's request token.</param> + /// <returns> + /// True if the request token has already been fully authorized by the user + /// who owns the relevant protected resources. False if the token has not yet + /// been authorized, has expired or does not exist. + /// </returns> + public bool IsRequestTokenAuthorized(string requestToken) { + throw new NotImplementedException(); + } + + public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { + this.tokensAndSecrets.Remove(requestToken); + this.tokensAndSecrets[accessToken] = accessTokenSecret; + } + + /// <summary> + /// Classifies a token as a request token or an access token. + /// </summary> + /// <param name="token">The token to classify.</param> + /// <returns>Request or Access token, or invalid if the token is not recognized.</returns> + public TokenType GetTokenType(string token) { + throw new NotImplementedException(); + } + + #endregion + + #region IOpenIdOAuthTokenManager Members + + public void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization) { + this.tokensAndSecrets[authorization.RequestToken] = string.Empty; + } + + #endregion + } +}
\ No newline at end of file diff --git a/samples/OpenIdRelyingPartyWebForms/Code/State.cs b/samples/OpenIdRelyingPartyWebForms/Code/State.cs index 4861a34..c8147e5 100644 --- a/samples/OpenIdRelyingPartyWebForms/Code/State.cs +++ b/samples/OpenIdRelyingPartyWebForms/Code/State.cs @@ -1,6 +1,6 @@ namespace OpenIdRelyingPartyWebForms { - using System.Collections.Generic; using System.Web; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; @@ -13,6 +13,11 @@ namespace OpenIdRelyingPartyWebForms { set { HttpContext.Current.Session["ProfileFields"] = value; } } + public static FetchResponse FetchResponse { + get { return HttpContext.Current.Session["FetchResponse"] as FetchResponse; } + set { HttpContext.Current.Session["FetchResponse"] = value; } + } + public static string FriendlyLoginName { get { return HttpContext.Current.Session["FriendlyUsername"] as string; } set { HttpContext.Current.Session["FriendlyUsername"] = value; } @@ -23,10 +28,17 @@ namespace OpenIdRelyingPartyWebForms { set { HttpContext.Current.Session["PapePolicies"] = value; } } + public static string GoogleAccessToken { + get { return HttpContext.Current.Session["GoogleAccessToken"] as string; } + set { HttpContext.Current.Session["GoogleAccessToken"] = value; } + } + public static void Clear() { ProfileFields = null; + FetchResponse = null; FriendlyLoginName = null; PapePolicies = null; + GoogleAccessToken = null; } } }
\ No newline at end of file diff --git a/samples/OpenIdRelyingPartyWebForms/Global.asax.cs b/samples/OpenIdRelyingPartyWebForms/Global.asax.cs index c7d1e8b..ac74853 100644 --- a/samples/OpenIdRelyingPartyWebForms/Global.asax.cs +++ b/samples/OpenIdRelyingPartyWebForms/Global.asax.cs @@ -1,15 +1,47 @@ namespace OpenIdRelyingPartyWebForms { using System; using System.Collections.Specialized; + using System.Configuration; using System.IO; using System.Text; using System.Web; + using DotNetOpenAuth.ApplicationBlock; + using DotNetOpenAuth.OAuth; + using OpenIdRelyingPartyWebForms.Code; public class Global : HttpApplication { public static log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(Global)); internal static StringBuilder LogMessages = new StringBuilder(); + internal static WebConsumer GoogleWebConsumer { + get { + var googleWebConsumer = (WebConsumer)HttpContext.Current.Application["GoogleWebConsumer"]; + if (googleWebConsumer == null) { + googleWebConsumer = new WebConsumer(GoogleConsumer.ServiceDescription, GoogleTokenManager); + HttpContext.Current.Application["GoogleWebConsumer"] = googleWebConsumer; + } + + return googleWebConsumer; + } + } + + internal static InMemoryTokenManager GoogleTokenManager { + get { + var tokenManager = (InMemoryTokenManager)HttpContext.Current.Application["GoogleTokenManager"]; + if (tokenManager == null) { + string consumerKey = ConfigurationManager.AppSettings["googleConsumerKey"]; + string consumerSecret = ConfigurationManager.AppSettings["googleConsumerSecret"]; + if (!string.IsNullOrEmpty(consumerKey)) { + tokenManager = new InMemoryTokenManager(consumerKey, consumerSecret); + HttpContext.Current.Application["GoogleTokenManager"] = tokenManager; + } + } + + return tokenManager; + } + } + public static string ToString(NameValueCollection collection) { using (StringWriter sw = new StringWriter()) { foreach (string key in collection.Keys) { diff --git a/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx b/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx new file mode 100644 index 0000000..7d5a54f --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx @@ -0,0 +1,22 @@ +<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/Site.Master" CodeBehind="DisplayGoogleContacts.aspx.cs" + Inherits="OpenIdRelyingPartyWebForms.MembersOnly.DisplayGoogleContacts" %> + +<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="Main"> + <asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex="0"> + <asp:View ID="View1" runat="server"> + <p>Obtain an access token by <asp:HyperLink NavigateUrl="~/loginPlusOAuth.aspx" runat="server" + Text="logging in at our OpenID+OAuth hybrid login page" />. </p> + <p>If you've already done that, then you might have inadvertently clicked "Allow [this + site] to remember me", which causes Google to stop sending the access token that + this sample doesn't save. If you did check it, you can restore this sample's + functionality by <a href="https://www.google.com/accounts/IssuedAuthSubTokens">revoking + access</a> to this site from your Google Account. </p> + </asp:View> + <asp:View ID="View2" runat="server"> + <h2>Address book</h2> + <p>These are the contacts for Google Account: <asp:Label ID="emailLabel" runat="server" + Font-Bold="True" /> and OpenID <asp:Label ID="claimedIdLabel" runat="server" Font-Bold="True" /></p> + <asp:PlaceHolder ID="resultsPlaceholder" runat="server" /> + </asp:View> + </asp:MultiView> +</asp:Content> diff --git a/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx.cs b/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx.cs new file mode 100644 index 0000000..b14aba1 --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx.cs @@ -0,0 +1,41 @@ +namespace OpenIdRelyingPartyWebForms.MembersOnly { + using System; + using System.Linq; + using System.Text; + using System.Web; + using System.Web.UI.WebControls; + using System.Xml.Linq; + using DotNetOpenAuth.ApplicationBlock; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + + public partial class DisplayGoogleContacts : System.Web.UI.Page { + protected void Page_Load(object sender, EventArgs e) { + if (!string.IsNullOrEmpty(State.GoogleAccessToken)) { + this.MultiView1.ActiveViewIndex = 1; + if (State.FetchResponse != null && State.FetchResponse.Attributes.Contains(WellKnownAttributes.Contact.Email)) { + this.emailLabel.Text = State.FetchResponse.Attributes[WellKnownAttributes.Contact.Email].Values[0]; + } else { + this.emailLabel.Text = "unavailable"; + } + this.claimedIdLabel.Text = this.User.Identity.Name; + var contactsDocument = GoogleConsumer.GetContacts(Global.GoogleWebConsumer, State.GoogleAccessToken); + this.RenderContacts(contactsDocument); + } + } + + private void RenderContacts(XDocument contactsDocument) { + var contacts = from entry in contactsDocument.Root.Elements(XName.Get("entry", "http://www.w3.org/2005/Atom")) + select new { Name = entry.Element(XName.Get("title", "http://www.w3.org/2005/Atom")).Value, Email = entry.Element(XName.Get("email", "http://schemas.google.com/g/2005")).Attribute("address").Value }; + StringBuilder tableBuilder = new StringBuilder(); + tableBuilder.Append("<table><tr><td>Name</td><td>Email</td></tr>"); + foreach (var contact in contacts) { + tableBuilder.AppendFormat( + "<tr><td>{0}</td><td>{1}</td></tr>", + HttpUtility.HtmlEncode(contact.Name), + HttpUtility.HtmlEncode(contact.Email)); + } + tableBuilder.Append("</table>"); + this.resultsPlaceholder.Controls.Add(new Literal { Text = tableBuilder.ToString() }); + } + } +} diff --git a/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx.designer.cs b/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx.designer.cs new file mode 100644 index 0000000..5cc5894 --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx.designer.cs @@ -0,0 +1,70 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4918 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace OpenIdRelyingPartyWebForms.MembersOnly { + + + public partial class DisplayGoogleContacts { + + /// <summary> + /// MultiView1 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.MultiView MultiView1; + + /// <summary> + /// View1 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.View View1; + + /// <summary> + /// View2 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.View View2; + + /// <summary> + /// emailLabel 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 emailLabel; + + /// <summary> + /// claimedIdLabel 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 claimedIdLabel; + + /// <summary> + /// resultsPlaceholder 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.PlaceHolder resultsPlaceholder; + } +} diff --git a/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj b/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj index cf40440..c45f007 100644 --- a/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj +++ b/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj @@ -98,6 +98,7 @@ <DesignTime>True</DesignTime> <DependentUpon>CustomStoreDataSet.xsd</DependentUpon> </Compile> + <Compile Include="Code\InMemoryTokenManager.cs" /> <Compile Include="Code\State.cs" /> <Compile Include="Code\TracePageAppender.cs" /> <Compile Include="Global.asax.cs"> @@ -110,6 +111,13 @@ <Compile Include="login.aspx.designer.cs"> <DependentUpon>login.aspx</DependentUpon> </Compile> + <Compile Include="loginPlusOAuth.aspx.cs"> + <DependentUpon>loginPlusOAuth.aspx</DependentUpon> + <SubType>ASPXCodeBehind</SubType> + </Compile> + <Compile Include="loginPlusOAuth.aspx.designer.cs"> + <DependentUpon>loginPlusOAuth.aspx</DependentUpon> + </Compile> <Compile Include="loginProgrammatic.aspx.cs"> <DependentUpon>loginProgrammatic.aspx</DependentUpon> <SubType>ASPXCodeBehind</SubType> @@ -117,6 +125,13 @@ <Compile Include="loginProgrammatic.aspx.designer.cs"> <DependentUpon>loginProgrammatic.aspx</DependentUpon> </Compile> + <Compile Include="MembersOnly\DisplayGoogleContacts.aspx.cs"> + <DependentUpon>DisplayGoogleContacts.aspx</DependentUpon> + <SubType>ASPXCodeBehind</SubType> + </Compile> + <Compile Include="MembersOnly\DisplayGoogleContacts.aspx.designer.cs"> + <DependentUpon>DisplayGoogleContacts.aspx</DependentUpon> + </Compile> <Compile Include="m\Login.aspx.cs"> <DependentUpon>Login.aspx</DependentUpon> <SubType>ASPXCodeBehind</SubType> @@ -144,6 +159,8 @@ <Content Include="images\dotnetopenid_tiny.gif" /> <Content Include="images\openid_login.gif" /> <Content Include="images\yahoo.png" /> + <Content Include="loginPlusOAuth.aspx" /> + <Content Include="MembersOnly\DisplayGoogleContacts.aspx" /> <Content Include="MembersOnly\Web.config" /> <Content Include="m\Login.aspx" /> </ItemGroup> @@ -165,6 +182,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="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" /> diff --git a/samples/OpenIdRelyingPartyWebForms/Web.config b/samples/OpenIdRelyingPartyWebForms/Web.config index a4cb801..7983e21 100644 --- a/samples/OpenIdRelyingPartyWebForms/Web.config +++ b/samples/OpenIdRelyingPartyWebForms/Web.config @@ -43,6 +43,14 @@ </messaging> </dotNetOpenAuth> + <appSettings> + <!-- Fill in your various consumer keys and secrets here to make the sample work. --> + <!-- You must get these values by signing up with each individual service provider. --> + <!-- Google sign-up: https://www.google.com/accounts/ManageDomains --> + <add key="googleConsumerKey" value="demo.dotnetopenauth.net"/> + <add key="googleConsumerSecret" value="5Yv1TfKm1551QrXZ9GpqepeD"/> + </appSettings> + <system.web> <!--<sessionState cookieless="true" />--> <compilation debug="true"/> diff --git a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx new file mode 100644 index 0000000..57bca52 --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx @@ -0,0 +1,30 @@ +<%@ Page Language="C#" AutoEventWireup="True" CodeBehind="loginPlusOAuth.aspx.cs" + Inherits="OpenIdRelyingPartyWebForms.loginPlusOAuth" ValidateRequest="false" + MasterPageFile="~/Site.Master" %> + +<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.RelyingParty" + TagPrefix="rp" %> +<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="Main"> + <h2>Login Page </h2> + <asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex='0'> + <asp:View ID="View1" runat="server"> + <p><b>Important note:</b> Do <b>not</b> check the "Allow [this site] to remember me" + check box while Google is asking for verification. Doing so will make this + sample only work once for your account. If you do check it, you can restore this + sample's functionality by <a href="https://www.google.com/accounts/IssuedAuthSubTokens"> + revoking access</a> to this site from your Google Account. </p> + <asp:Button ID="beginButton" runat="server" Text="Login and get Gmail Contacts" OnClick="beginButton_Click" /> + <p>Due to the way Google matches realms and consumer keys, this demo will only work + when it is run under http://demo.dotnetopenauth.net/. By registering your own consumer + key with Google and changing the configuration of this sample, you can run it on + your own public web site, but it can never work from a private (localhost or firewall-protected) + address. </p> + </asp:View> + <asp:View ID="AuthorizationDenied" runat="server"> + Authentication succeeded, but Gmail Contacts access was denied. + </asp:View> + <asp:View ID="AuthenticationFailed" runat="server"> + Authentication failed or was canceled. + </asp:View> + </asp:MultiView> +</asp:Content> diff --git a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.cs b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.cs new file mode 100644 index 0000000..d4e9885 --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.cs @@ -0,0 +1,71 @@ +namespace OpenIdRelyingPartyWebForms { + using System; + using System.Web.Security; + using DotNetOpenAuth.ApplicationBlock; + using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.RelyingParty; + + public partial class loginPlusOAuth : System.Web.UI.Page { + private const string GoogleOPIdentifier = "https://www.google.com/accounts/o8/id"; + private static readonly OpenIdRelyingParty relyingParty = new OpenIdRelyingParty(); + + protected void Page_Load(object sender, EventArgs e) { + if (!IsPostBack && string.Equals(Request.Url.Host, "localhost", StringComparison.OrdinalIgnoreCase)) { + // Disable the button since the scenario won't work under localhost, + // and this will help encourage the user to read the the text above the button. + this.beginButton.Enabled = false; + } + + IAuthenticationResponse authResponse = relyingParty.GetResponse(); + if (authResponse != null) { + switch (authResponse.Status) { + case AuthenticationStatus.Authenticated: + State.FetchResponse = authResponse.GetExtension<FetchResponse>(); + AuthorizedTokenResponse accessToken = Global.GoogleWebConsumer.ProcessUserAuthorization(authResponse); + if (accessToken != null) { + State.GoogleAccessToken = accessToken.AccessToken; + FormsAuthentication.SetAuthCookie(authResponse.ClaimedIdentifier, false); + Response.Redirect("~/MembersOnly/DisplayGoogleContacts.aspx"); + } else { + MultiView1.SetActiveView(AuthorizationDenied); + } + break; + case AuthenticationStatus.Canceled: + case AuthenticationStatus.Failed: + default: + this.MultiView1.SetActiveView(this.AuthenticationFailed); + break; + } + } + } + + protected void beginButton_Click(object sender, EventArgs e) { + this.GetGoogleRequest().RedirectToProvider(); + } + + private IAuthenticationRequest GetGoogleRequest() { + // Google requires that the realm and consumer key be equal, + // so we constrain the realm to match the realm in the web.config file. + // This does mean that the return_to URL must also fall under the key, + // which means this sample will only work on a public web site + // that is properly registered with Google. + // We will customize the realm to use http or https based on what the + // return_to URL will be (which will be this page). + Realm realm = Request.Url.Scheme + Uri.SchemeDelimiter + Global.GoogleTokenManager.ConsumerKey + "/"; + IAuthenticationRequest authReq = relyingParty.CreateRequest(GoogleOPIdentifier, realm); + + // Prepare the OAuth extension + string scope = GoogleConsumer.GetScopeUri(GoogleConsumer.Applications.Contacts); + Global.GoogleWebConsumer.AttachAuthorizationRequest(authReq, scope); + + // We also want the user's email address + var fetch = new FetchRequest(); + fetch.Attributes.AddRequired(WellKnownAttributes.Contact.Email); + authReq.AddExtension(fetch); + + return authReq; + } + } +} diff --git a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.designer.cs b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.designer.cs new file mode 100644 index 0000000..b9c836d --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.designer.cs @@ -0,0 +1,61 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4918 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace OpenIdRelyingPartyWebForms { + + + public partial class loginPlusOAuth { + + /// <summary> + /// MultiView1 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.MultiView MultiView1; + + /// <summary> + /// View1 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.View View1; + + /// <summary> + /// beginButton 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 beginButton; + + /// <summary> + /// AuthorizationDenied 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.View AuthorizationDenied; + + /// <summary> + /// AuthenticationFailed 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.View AuthenticationFailed; + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs b/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs index dd59bae..05ac306 100644 --- a/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs @@ -26,9 +26,54 @@ namespace DotNetOpenAuth.Test.Messaging { Assert.AreEqual(request.Url.Query, info.Query); Assert.AreEqual(request.QueryString["a"], info.QueryString["a"]); Assert.AreEqual(request.Url, info.Url); + Assert.AreEqual(request.Url, info.UrlBeforeRewriting); Assert.AreEqual(request.HttpMethod, info.HttpMethod); } + // All these tests are ineffective because ServerVariables[] cannot be set. + ////[TestMethod] + ////public void CtorRequestWithDifferentPublicHttpHost() { + //// HttpRequest request = new HttpRequest("file", "http://someserver?a=b", "a=b"); + //// request.ServerVariables["HTTP_HOST"] = "publichost"; + //// HttpRequestInfo info = new HttpRequestInfo(request); + //// Assert.AreEqual("publichost", info.UrlBeforeRewriting.Host); + //// Assert.AreEqual(80, info.UrlBeforeRewriting.Port); + //// Assert.AreEqual(request.Url.Query, info.Query); + //// Assert.AreEqual(request.QueryString["a"], info.QueryString["a"]); + ////} + + ////[TestMethod] + ////public void CtorRequestWithDifferentPublicHttpsHost() { + //// HttpRequest request = new HttpRequest("file", "https://someserver?a=b", "a=b"); + //// request.ServerVariables["HTTP_HOST"] = "publichost"; + //// HttpRequestInfo info = new HttpRequestInfo(request); + //// Assert.AreEqual("publichost", info.UrlBeforeRewriting.Host); + //// Assert.AreEqual(443, info.UrlBeforeRewriting.Port); + //// Assert.AreEqual(request.Url.Query, info.Query); + //// Assert.AreEqual(request.QueryString["a"], info.QueryString["a"]); + ////} + + ////[TestMethod] + ////public void CtorRequestWithDifferentPublicHostNonstandardPort() { + //// HttpRequest request = new HttpRequest("file", "http://someserver?a=b", "a=b"); + //// request.ServerVariables["HTTP_HOST"] = "publichost:550"; + //// HttpRequestInfo info = new HttpRequestInfo(request); + //// Assert.AreEqual("publichost", info.UrlBeforeRewriting.Host); + //// Assert.AreEqual(550, info.UrlBeforeRewriting.Port); + //// Assert.AreEqual(request.Url.Query, info.Query); + //// Assert.AreEqual(request.QueryString["a"], info.QueryString["a"]); + ////} + + ////[TestMethod] + ////public void CtorRequestWithDifferentPublicIPv6Host() { + //// HttpRequest request = new HttpRequest("file", "http://[fe80::587e:c6e5:d3aa:657a]:8089/v3.1/", ""); + //// request.ServerVariables["HTTP_HOST"] = "[fe80::587e:c6e5:d3aa:657b]:8089"; + //// HttpRequestInfo info = new HttpRequestInfo(request); + //// Assert.AreEqual("[fe80::587e:c6e5:d3aa:657b]", info.UrlBeforeRewriting.Host); + //// Assert.AreEqual(8089, info.UrlBeforeRewriting.Port); + //// Assert.AreEqual(request.Url.Query, info.Query); + ////} + /// <summary> /// Checks that a property dependent on another null property /// doesn't generate a NullReferenceException. diff --git a/src/DotNetOpenAuth.Test/Messaging/MessagingTestBase.cs b/src/DotNetOpenAuth.Test/Messaging/MessagingTestBase.cs index 0a11a75..accb182 100644 --- a/src/DotNetOpenAuth.Test/Messaging/MessagingTestBase.cs +++ b/src/DotNetOpenAuth.Test/Messaging/MessagingTestBase.cs @@ -70,7 +70,7 @@ namespace DotNetOpenAuth.Test { } HttpRequestInfo request = new HttpRequestInfo { HttpMethod = method, - Url = requestUri.Uri, + UrlBeforeRewriting = requestUri.Uri, Headers = headers, InputStream = ms, }; diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDictionaryTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDictionaryTests.cs index 7083b1e..24171e1 100644 --- a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDictionaryTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessageDictionaryTests.cs @@ -331,17 +331,26 @@ namespace DotNetOpenAuth.Test.Messaging.Reflection { /// A test for System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String,System.String<<.Clear /// </summary> [TestMethod] - public void Clear() { - ICollection<KeyValuePair<string, string>> target = this.MessageDescriptions.GetAccessor(this.message); + public void ClearValues() { + MessageDictionary target = this.MessageDescriptions.GetAccessor(this.message); IDictionary<string, string> targetAsDictionary = ((IDictionary<string, string>)target); this.message.Name = "Andrew"; this.message.Age = 15; targetAsDictionary["extra"] = "value"; - target.Clear(); + target.ClearValues(); Assert.AreEqual(2, target.Count, "Clearing should remove all keys except for declared non-nullable structs."); Assert.IsFalse(targetAsDictionary.ContainsKey("extra")); Assert.IsNull(this.message.Name); Assert.AreEqual(0, this.message.Age); } + + /// <summary> + /// Verifies that the Clear method throws the expected exception. + /// </summary> + [TestMethod, ExpectedException(typeof(NotSupportedException))] + public void Clear() { + MessageDictionary target = this.MessageDescriptions.GetAccessor(this.message); + target.Clear(); + } } } diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs index 0215801..19e6a82 100644 --- a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs @@ -82,7 +82,7 @@ namespace DotNetOpenAuth.Test.Messaging.Reflection { Assert.AreEqual("abc", part.GetValue(message)); } - [TestMethod, ExpectedException(typeof(ArgumentException))] + [TestMethod, ExpectedException(typeof(ProtocolException))] public void ConstantFieldMemberInvalidValues() { var message = new MessageWithConstantField(); MessagePart part = GetMessagePart(message.GetType(), "ConstantField"); diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs index 2e5a9ce..46e3373 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingHttpRequestInfo.cs @@ -41,7 +41,7 @@ namespace DotNetOpenAuth.Test.Mocks { internal CoordinatingHttpRequestInfo(MessageReceivingEndpoint recipient) { this.recipient = recipient; if (recipient != null) { - this.Url = recipient.Location; + this.UrlBeforeRewriting = recipient.Location; } if (recipient == null || (recipient.AllowedMethods & HttpDeliveryMethods.GetRequest) != 0) { diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs index 2d14bc8..10b0261 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs @@ -110,7 +110,7 @@ namespace DotNetOpenAuth.Test.Mocks { if (signedMessage != null) { string httpMethod = this.GetHttpMethod(signedMessage.HttpMethods); requestInfo.HttpMethod = httpMethod; - requestInfo.Url = message.Recipient; + requestInfo.UrlBeforeRewriting = message.Recipient; signedMessage.HttpMethod = httpMethod; } diff --git a/src/DotNetOpenAuth.Test/Mocks/MockOpenIdExtension.cs b/src/DotNetOpenAuth.Test/Mocks/MockOpenIdExtension.cs index 0010bb9..f9d418f 100644 --- a/src/DotNetOpenAuth.Test/Mocks/MockOpenIdExtension.cs +++ b/src/DotNetOpenAuth.Test/Mocks/MockOpenIdExtension.cs @@ -48,6 +48,15 @@ namespace DotNetOpenAuth.Test.Mocks { get { return Enumerable.Empty<string>(); } } + /// <summary> + /// Gets or sets a value indicating whether this extension was + /// signed by the OpenID Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the provider; otherwise, <c>false</c>. + /// </value> + public bool IsSignedByRemoteParty { get; set; } + #endregion #region IMessage Properties diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs index 82d211a..856f164 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs @@ -83,9 +83,9 @@ namespace DotNetOpenAuth.Test.ChannelElements { HttpRequestInfo requestInfo = CreateHttpRequestInfo(HttpDeliveryMethods.PostRequest, fields); // Now add another field to the request URL - UriBuilder builder = new UriBuilder(requestInfo.Url); + UriBuilder builder = new UriBuilder(requestInfo.UrlBeforeRewriting); builder.Query = "Name=Andrew"; - requestInfo.Url = builder.Uri; + requestInfo.UrlBeforeRewriting = builder.Uri; requestInfo.RawUrl = builder.Path + builder.Query + builder.Fragment; // Finally, add an Authorization header @@ -288,7 +288,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { } HttpRequestInfo request = new HttpRequestInfo { HttpMethod = method, - Url = requestUri.Uri, + UrlBeforeRewriting = requestUri.Uri, RawUrl = requestUri.Path + requestUri.Query + requestUri.Fragment, Headers = headers, InputStream = ms, @@ -300,7 +300,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { private static HttpRequestInfo ConvertToRequestInfo(HttpWebRequest request, Stream postEntity) { HttpRequestInfo info = new HttpRequestInfo { HttpMethod = request.Method, - Url = request.RequestUri, + UrlBeforeRewriting = request.RequestUri, RawUrl = request.RequestUri.AbsolutePath + request.RequestUri.Query + request.RequestUri.Fragment, Headers = request.Headers, InputStream = postEntity, diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/ExtensionsBindingElementTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/ExtensionsBindingElementTests.cs index 28fe2cc..12f6e7a 100644 --- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/ExtensionsBindingElementTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/ExtensionsBindingElementTests.cs @@ -31,7 +31,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { this.factory = new StandardOpenIdExtensionFactory(); this.factory.RegisterExtension(MockOpenIdExtension.Factory); - this.rpElement = new ExtensionsBindingElement(this.factory, new RelyingPartySecuritySettings()); + this.rpElement = new ExtensionsBindingElement(this.factory); this.rpElement.Channel = new TestChannel(this.MessageDescriptions); this.request = new SignedResponseRequest(Protocol.Default.Version, OpenIdTestBase.OPUri, AuthenticationRequestMode.Immediate); } @@ -113,15 +113,17 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { /// Verifies that unsigned extension responses (where any or all fields are unsigned) are ignored. /// </summary> [TestMethod] - public void UnsignedExtensionsAreIgnored() { + public void ExtensionsAreIdentifiedAsSignedOrUnsigned() { Protocol protocol = Protocol.Default; OpenIdCoordinator coordinator = new OpenIdCoordinator( rp => { RegisterMockExtension(rp.Channel); var response = rp.Channel.ReadFromRequest<IndirectSignedResponse>(); - Assert.AreEqual(1, response.Extensions.Count, "Signed extension should have been received."); + Assert.AreEqual(1, response.SignedExtensions.Count(), "Signed extension should have been received."); + Assert.AreEqual(0, response.UnsignedExtensions.Count(), "No unsigned extension should be present."); response = rp.Channel.ReadFromRequest<IndirectSignedResponse>(); - Assert.AreEqual(0, response.Extensions.Count, "Unsigned extension should have been ignored."); + Assert.AreEqual(0, response.SignedExtensions.Count(), "No signed extension should have been received."); + Assert.AreEqual(1, response.UnsignedExtensions.Count(), "Unsigned extension should have been received."); }, op => { RegisterMockExtension(op.Channel); diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/AuthenticationRequestTest.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/AuthenticationRequestTest.cs index cb898be..9803095 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/AuthenticationRequestTest.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/AuthenticationRequestTest.cs @@ -34,7 +34,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { Assert.IsNotNull(userSetupUrl); // Now construct a new request as if it had just come in. - HttpRequestInfo httpRequest = new HttpRequestInfo { Url = userSetupUrl }; + HttpRequestInfo httpRequest = new HttpRequestInfo { UrlBeforeRewriting = userSetupUrl }; var setupRequest = AuthenticationRequest_Accessor.AttachShadow(provider.GetRequest(httpRequest)); CheckIdRequest_Accessor setupRequestMessage = setupRequest.RequestMessage; diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs index 82f9ecc..0a6cdcc 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs @@ -91,7 +91,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { [TestMethod] public void GetRequest() { HttpRequestInfo httpInfo = new HttpRequestInfo(); - httpInfo.Url = new Uri("http://someUri"); + httpInfo.UrlBeforeRewriting = new Uri("http://someUri"); Assert.IsNull(this.provider.GetRequest(httpInfo), "An irrelevant request should return null."); var providerDescription = new ProviderEndpointDescription(OpenIdTestBase.OPUri, Protocol.Default.Version); diff --git a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs index c5257a6..083b988 100644 --- a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs @@ -39,8 +39,8 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty { Assert.IsNull(authResponse.Exception); Assert.AreEqual<string>(assertion.ClaimedIdentifier, authResponse.ClaimedIdentifier); Assert.AreEqual<string>(authResponseAccessor.endpoint.FriendlyIdentifierForDisplay, authResponse.FriendlyIdentifierForDisplay); - Assert.AreSame(extension, authResponse.GetExtension(typeof(ClaimsResponse))); - Assert.AreSame(extension, authResponse.GetExtension<ClaimsResponse>()); + Assert.AreSame(extension, authResponse.GetUntrustedExtension(typeof(ClaimsResponse))); + Assert.AreSame(extension, authResponse.GetUntrustedExtension<ClaimsResponse>()); Assert.IsNull(authResponse.GetCallbackArgument("a")); Assert.AreEqual(0, authResponse.GetCallbackArguments().Count); } diff --git a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/RelyingPartySecuritySettingsTests.cs b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/RelyingPartySecuritySettingsTests.cs index 8c5dc6a..cb5fbb5 100644 --- a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/RelyingPartySecuritySettingsTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/RelyingPartySecuritySettingsTests.cs @@ -53,13 +53,5 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty { this.settings.RequireSsl = !this.settings.RequireSsl; Assert.IsTrue(requireSslChanged); } - - /// <summary> - /// Verifies default value for AllowUnsignedIncomingExtensions. - /// </summary> - [TestMethod] - public void AllowUnsignedIncomingExtensionsDefault() { - Assert.IsFalse(this.settings.AllowUnsignedIncomingExtensions); - } } } diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln index 4f15700..b9c0328 100644 --- a/src/DotNetOpenAuth.sln +++ b/src/DotNetOpenAuth.sln @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Specs", "Specs", "{CD57219F ProjectSection(SolutionItems) = preProject ..\doc\specs\OAuth Core 1.0.htm = ..\doc\specs\OAuth Core 1.0.htm ..\doc\specs\OAuth Core 1.0a (Draft 3).htm = ..\doc\specs\OAuth Core 1.0a (Draft 3).htm + ..\doc\specs\OpenID OAuth Extension.htm = ..\doc\specs\OpenID OAuth Extension.htm ..\doc\specs\openid-attribute-exchange-1_0.html = ..\doc\specs\openid-attribute-exchange-1_0.html ..\doc\specs\openid-authentication-1_1.html = ..\doc\specs\openid-authentication-1_1.html ..\doc\specs\openid-authentication-2_0.html = ..\doc\specs\openid-authentication-2_0.html @@ -156,6 +157,10 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "OAuthServiceProvider", "..\ VWDDynamicPort = "false" EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIdOfflineProvider", "..\samples\OpenIdOfflineProvider\OpenIdOfflineProvider.csproj", "{5C65603B-235F-47E6-B536-06385C60DE7F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{E9ED920D-1F83-48C0-9A4B-09CCE505FE6D}" +EndProject Global GlobalSection(TestCaseManagementSettings) = postSolution CategoryFile = DotNetOpenAuth.vsmdi @@ -244,6 +249,12 @@ Global {7ADCCD5C-AC2B-4340-9410-FE3A31A48191}.Debug|Any CPU.Build.0 = Debug|Any CPU {7ADCCD5C-AC2B-4340-9410-FE3A31A48191}.Release|Any CPU.ActiveCfg = Debug|Any CPU {7ADCCD5C-AC2B-4340-9410-FE3A31A48191}.Release|Any CPU.Build.0 = Debug|Any CPU + {5C65603B-235F-47E6-B536-06385C60DE7F}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {5C65603B-235F-47E6-B536-06385C60DE7F}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {5C65603B-235F-47E6-B536-06385C60DE7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C65603B-235F-47E6-B536-06385C60DE7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C65603B-235F-47E6-B536-06385C60DE7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C65603B-235F-47E6-B536-06385C60DE7F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -263,5 +274,6 @@ Global {6EC36418-DBC5-4AD1-A402-413604AA7A08} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} {9ADBE36D-9960-48F6-82E9-B4AC559E9AC3} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} {7ADCCD5C-AC2B-4340-9410-FE3A31A48191} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} + {5C65603B-235F-47E6-B536-06385C60DE7F} = {E9ED920D-1F83-48C0-9A4B-09CCE505FE6D} EndGlobalSection EndGlobal diff --git a/src/DotNetOpenAuth.vsmdi b/src/DotNetOpenAuth.vsmdi index 490e004..4a898c4 100644 --- a/src/DotNetOpenAuth.vsmdi +++ b/src/DotNetOpenAuth.vsmdi @@ -38,6 +38,7 @@ <TestLink id="3e2f1dad-3684-587c-9039-8d116582be10" name="GetReturnToArgumentEmptyKey" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="46ec24da-deb7-27c7-6dc6-52090e4fd1fb" name="Serialize" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a6e464af-42df-1ba4-17e5-b955352664b5" name="RPOnlyRenegotiatesOnce" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="6c95f443-463e-2856-f500-b9029645e44c" name="RequestNullRecipient" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="9d4a230d-9e74-dc1b-ecdc-bf875b56e1b3" name="CtorNullVersion" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="04be6602-31a2-f4ae-8fdb-b9ad2ac370be" name="PrepareMessageForReceiving" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="90557d85-db17-e9ab-e17b-32d6cc9fd437" name="TrimFragment" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -99,6 +100,7 @@ <TestLink id="5803e93d-e256-86ba-e10e-499d2f813c6d" name="Trivial" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="da8fcfa9-bd2c-eca0-ecbf-90364f84e8e5" name="AddExtraFieldThatAlreadyExists" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="00ed61cd-46cd-9c0e-f044-38d784c8bcfb" name="DecodeEmptyStringFails" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="29e45877-ca7a-85de-5c39-6d43befe1a1e" name="DiscoveryRequireSslWithInsecureXrdsButSecureLinkTags" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="4a009f39-66b1-9cc5-ea8b-13b75ab22a5b" name="ContainsKey" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1f3ea08b-9880-635f-368f-9fcd3e25f3cd" name="ReadFromRequestNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f1e1aa37-c712-6096-22fa-394008f0820a" name="CtorNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -133,6 +135,7 @@ <TestLink id="c2c78c43-7f50-ffc3-affb-e60de2b76c94" name="CreateQueryStringNullDictionary" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="8538caf8-48bd-7cf8-6ad8-15e1c3766f92" name="CtorNullType" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="fba4d9a6-d8c7-a063-7c07-4a27c38c94a9" name="InvalidRealmBadWildcard3" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="10245b55-8130-e0aa-e211-4a16fa14d0b1" name="ClearValues" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f41ce7ab-5500-7eea-ab4d-8c646bffff23" name="HttpSchemePrepended" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="17267cde-a296-8293-5bd1-9ca629817e4b" name="OpenIdRelyingParty" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="abb0610a-c06f-0767-ac99-f37a2b573d1b" name="ParameterPrefix" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -170,7 +173,7 @@ <TestLink id="63944cb8-4c61-c42c-906f-986fa793370b" name="SignatureTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2a7b77c3-27d5-7788-e664-5d20118d223b" name="OPRejectsHttpNoEncryptionAssociateRequests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="98e7a0f9-ab6c-7ff1-3a2c-00d8244e1bec" name="CommonMethods" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="385c302d-b546-c164-2a59-2e35f75d7d60" name="RemoveStructDeclaredProperty" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="93c157e8-1293-3aff-f616-66502872b37d" name="DiscoveryRequiresSslIgnoresInsecureEndpointsInXrds" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e137b84a-d2a7-9af6-d15d-a92417668ccf" name="Transport" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5218fba2-d1af-e1f4-7641-9ae1d4975430" name="DirectResponsesSentUsingKeyValueForm" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="643c9887-3f12-300e-fdac-17ae59652712" name="Mode" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -201,7 +204,7 @@ <TestLink id="cb9a8325-abf5-5d97-a94e-a6d34f2b51e1" name="AssociateRenegotiateLimitedByRPSecuritySettings" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="997253fb-7591-c151-1705-02976b400f27" name="AddAttributeTwice" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="97f0277a-86e6-5b5a-8419-c5253cabf2e0" name="UserAuthorizationUriTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="44ced969-83dd-201d-a660-e3744ee81cf8" name="ConstructorTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="e97cee09-4163-d83f-f65f-14e424294172" name="ExtensionsAreIdentifiedAsSignedOrUnsigned" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f787ae5d-b8fc-0862-a527-9157d11bbed7" name="UntrustedWebRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f583b298-139a-e733-dde6-f9dc4b73d4bf" name="SendDirectMessageResponseHonorsHttpStatusCodes" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5f02e24c-2972-c598-ca71-ea362b2fe7d8" name="SecuritySettingsSetNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -212,14 +215,14 @@ <TestLink id="f6979feb-7016-4e2b-14e2-e6c2c392419f" name="RemoveByKeyValuePair" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="454165a2-c26e-5740-09a9-d234db052ba3" name="InvalidRealmNullUri" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="03b47440-3d09-ab28-97f1-39809f5703b6" name="NormalizeCase" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="f4537b23-bb5e-5c6f-da53-64b34472f0dc" name="ChannelGetter" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="77047207-0571-72d5-71bd-586b878bcc0c" name="Base64Member" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1e2ae78c-d2f3-a808-2b82-eca9f9f2e458" name="Keys" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1d5fb5a9-e15c-d99c-7a7e-95a4c4d123c2" name="DirectRequestsUsePost" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="73c6c979-205d-2216-d98d-2dd136b352c6" name="UtcCreationDateConvertsToUniversal" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="4bd86299-18d7-abbe-e5d2-1afad17279e9" name="Parse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="fa2e5bbd-4c41-f2b1-e875-38c6ef011fa1" name="RandomCharactersTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="565140c9-c9fe-9466-1e39-740d7e368cb5" name="TryParse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="7ea157db-cf32-529f-f1d3-b3351f17725a" name="CtorSimpleServiceProvider" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="47706bc6-7bee-0385-62b4-4f9cec6cc702" name="CtorWithTextMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a778f331-f14e-9d6e-f942-a023423540f6" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="715dcbdd-28f5-3c33-7d88-e0a1b648d89a" name="CreateRequestDumbMode" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a8bd3730-1660-dca9-87ec-23bc9dc39ab9" name="CtorGoodXriSecure" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -235,16 +238,16 @@ <TestLink id="cbfeb75b-d031-7df3-c281-3c9e1c450042" name="CtorFromRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="182203f3-5a16-b736-ea8c-b59f6bf7df66" name="InvalidRealmTwoWildcards2" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3536ba12-fdb0-2ac9-3fef-00a2dd8e9a65" name="SharedAssociationTampered" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="b71d12f6-58a1-cf82-d06e-e57c0a3ea55c" name="RPRejectsUnencryptedSuggestion" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="ae384709-e9a4-0142-20ba-6adb6b40b3e2" name="CtorStringHttpsSchemeSecure" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="51a08d94-c327-4d28-1f0c-f7920ea54870" name="ValidMessageTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="809afd59-8f10-ce37-6630-06b59351a05a" name="CommonProperties" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="5dcd69c3-e979-7316-4551-a73fe4645dcd" name="SecuritySettings" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f44fb549-fc8a-7469-6eed-09d9f86cebff" name="SendDirectMessageResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3bb818b4-5423-ad91-8cd9-8606ec85d2cb" name="ReadFromRequestAuthorizationScattered" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="8aecb3a5-2cb5-143d-aa99-9514fa8dfacb" name="AddAttributeByValue" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="cd219db4-4f6e-8ff4-f957-c8428d38c118" name="HttpSignatureGeneration" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="38239ff0-1dfd-1116-55df-2790243dc768" name="IsValid" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="9104f36f-6652-dcbb-a8ae-0d6fc34d76ed" name="AddCallbackArgumentClearsPreviousArgument" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="0435e38a-71f2-d58d-9c07-d97d830a1578" name="ExtensionResponsesAreSigned" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="b71d12f6-58a1-cf82-d06e-e57c0a3ea55c" name="RPRejectsUnencryptedSuggestion" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="b63c4b89-3889-6dcf-8890-c92fc44c0b10" name="VerifyBadTimestampIsRejected" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="4c399759-263f-5eba-8855-de14f197e647" name="QueryStringContainPrefixedParametersNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="58f848e5-42d7-1508-f9b5-7691337e6da9" name="IsExtensionSupportedEmptyString" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -275,12 +278,11 @@ <TestLink id="8b11aa63-4c0f-41ff-f70c-882aacf939fe" name="CtorCountNegative" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5e0c892d-7ad8-6d56-1f1d-2fb6236670d6" name="CtorDefault" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="28fe030c-d36e-13cf-475c-7813210bf886" name="AddAttributeRequestAgain" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="93c157e8-1293-3aff-f616-66502872b37d" name="DiscoveryRequiresSslIgnoresInsecureEndpointsInXrds" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="64142858-d52e-be06-d11f-6be326c6176b" name="RespondTwoValues" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="f533bf9e-daa1-b26a-4789-372f3a9291d6" name="TryRequireSslAdjustsIdentifier" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1c5d54e2-d96a-d3a6-aeac-95f137b96421" name="CommonMethods" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="910f8448-5454-8ae5-cba3-690c7f375576" name="ParameterNames" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="507cd1b6-1010-0bca-cf7f-f96e3f4f6c6c" name="QueryBeforeSettingUrl" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="b211de14-4dd8-bd3e-dcf0-58f0c6aa0031" name="AllowUnsignedIncomingExtensionsDefault" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ef8a2274-4e58-0dde-4c5c-7f286865fc3a" name="SendReplayProtectedMessageSetsNonce" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="734dd45c-6320-26a9-e412-62ecacfd285a" name="CtorNullAttribute" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="439c8c16-2ba5-eb3b-b631-ce50ec48eba0" name="CtorNullMember" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -304,14 +306,14 @@ <TestLink id="ee7a04ba-0419-e08f-b838-01ec0f2a838e" name="UnsolicitedAssertion" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5f3758b3-1410-c742-e623-b964c01b0633" name="AuthenticationTimeUtcConvertsToUtc" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="547cfee6-bbb4-6138-a114-fc0eb6cc94f6" name="PrivateAssociationTampered" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="f69f1c0c-e258-95fb-4fcb-ad14bfc40e3c" name="Discover" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="385c302d-b546-c164-2a59-2e35f75d7d60" name="RemoveStructDeclaredProperty" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="dd5be0e2-a1fc-3369-0b11-78b728eeaba5" name="CtorNullUri" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="14acb719-f090-018f-b870-9a5acb1d7179" name="AddAuthLevelTypes" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="90d3c411-8895-a07f-7a21-258b9d43c5b2" name="InvalidMessageNoNonceReceivedTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="44091d36-98db-2115-8647-7bd7cd308796" name="ToStringTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3df1f62b-4fb4-d399-cf7f-40b72001d9d6" name="CtorUnsolicited" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="534bee09-36e1-c3e0-f6af-bc191b10aa48" name="CtorNullSigner" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="ae384709-e9a4-0142-20ba-6adb6b40b3e2" name="CtorStringHttpsSchemeSecure" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="44ced969-83dd-201d-a660-e3744ee81cf8" name="ConstructorTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3b70dd09-384d-5b99-222b-dc8ce8e791f2" name="SecuritySettingsSetNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="19d2219e-c04d-fa3a-5e26-92448f35f21d" name="RespondNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="152e7a3a-21f9-eabf-0065-08597a0cc9a6" name="AuthorizationHeaderScheme" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -359,7 +361,7 @@ <TestLink id="bb542259-4c10-4b88-1b3c-f842b0bb49a9" name="ImmediateVsSetupModes" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="68532d6d-a0cf-5883-17e2-6060707ba9ae" name="DecodeOobToNullUri" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2f5cfa57-bcb4-39af-e769-2d7c34e2598e" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="6c95f443-463e-2856-f500-b9029645e44c" name="RequestNullRecipient" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="809afd59-8f10-ce37-6630-06b59351a05a" name="CommonProperties" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ca9f3da7-e19f-b58b-54fe-54fa56ab9556" name="AddByKeyAndValue" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="30a8eab6-6423-26af-da1a-ec304935fe43" name="RemoveNonexistentHandle" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a59c5dc0-de4d-8136-8545-2e2e9616de46" name="SerializationWithXri" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -373,18 +375,15 @@ <TestLink id="3e676e31-3b6d-9d12-febd-d632ece804ec" name="RPRejectsMismatchingAssociationAndSessionBitLengths" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="54a65e0b-1857-72b9-797b-fe3d9a082131" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="eb932fc7-76c7-b63f-e1e6-a59dea8e4da1" name="AddAttribute" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="736a09b4-f56e-0176-6c1c-81db0fbe3412" name="CtorUriHttpsSchemeSecure" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="889ba616-43dc-8a7f-ee13-46288969d617" name="ParameterNames" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="cc9200bf-1399-d40a-9754-6415f0b7bcf8" name="CreateRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="0aa1bc22-0b26-3977-5983-5dc4a454cea5" name="OptionalNullableStruct" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="7ea157db-cf32-529f-f1d3-b3351f17725a" name="CtorSimpleServiceProvider" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="565140c9-c9fe-9466-1e39-740d7e368cb5" name="TryParse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a145f430-8062-5ad7-0cf5-b51eba0f8de7" name="HttpsSignatureGeneration" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3c67903e-15ce-9ed4-34c8-f77059af79ca" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7f3144c7-95a1-affa-1a37-9e6169c19be6" name="SharedAssociationNegative" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="870cce9d-5b17-953c-028f-827ec8b56da2" name="GetInvalidMessageType" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="09b892f2-96e9-45b7-d082-b0bb512c1dd4" name="RequiredNonNullableStruct" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5dd2e6c9-ff0f-48de-3a1a-cbab61370843" name="SetCountNegative" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="5dcd69c3-e979-7316-4551-a73fe4645dcd" name="SecuritySettings" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="40e1121e-8ff3-df73-203b-04baab671a0c" name="ImplicitConversionToStringTests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="724cc3e8-c13c-5cc6-ce14-25c51ad6297d" name="Mode" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="536ecd26-4bda-a35e-5af8-666eb9b44940" name="NullValueEncoding" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -397,11 +396,12 @@ <TestLink id="035cd43a-23d5-af91-12ee-0a0ce78b3548" name="XrdsDiscoveryFromHttpHeader" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="14ce54ee-5507-ac70-5514-99b7b83ba3d6" name="ExtensionFactories" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="9986fea9-8d64-9ada-60cb-ab95adb50fb7" name="ToStringDeferredEmptyMultiLine" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="d067c55c-3715-ed87-14a2-c07349813c94" name="IsDirectedIdentity" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ef20222d-b2e2-d593-17fa-512041020643" name="InvalidRealmNullString" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f20bd439-e277-dc27-4ec4-5d5949d1c6bf" name="RequestUsingAuthorizationHeaderScattered" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="d647fd93-40b3-24d5-25fc-661c0d58335c" name="SendIndirectMessageFormPostNullMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="01e33554-07cc-ff90-46f8-7d0ca036c9f6" name="ToDictionaryNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="1e915672-5691-70d6-163e-caf1a473f822" name="UnsignedExtensionsAreIgnored" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="889ba616-43dc-8a7f-ee13-46288969d617" name="ParameterNames" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e9a5efc6-fde8-8fa4-0bda-2675a4a7e06b" name="DefaultReferenceTypeDeclaredPropertyHasNoKey" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="84e718d7-bb82-e7d1-31be-471e2c154053" name="Item" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a260d196-066f-b0ae-a40e-fb9d962b28a4" name="XrdsDirectDiscovery_20" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -411,17 +411,17 @@ <TestLink id="9684f7bf-cdda-a2c5-0822-29cb0add3835" name="ResponseNonceGetter" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e3a3b3b6-e05f-0a99-e20c-af91a9065819" name="AssociateRequestDeterminedBySecuritySettings" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="54eae9ed-bed1-eeda-b6ea-045c8f7e2ba5" name="SendIndirectMessage301GetNullFields" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="d067c55c-3715-ed87-14a2-c07349813c94" name="IsDirectedIdentity" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="736a09b4-f56e-0176-6c1c-81db0fbe3412" name="CtorUriHttpsSchemeSecure" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="069995aa-4136-610b-3f41-df80a138c244" name="AppendQueryArgsNullUriBuilder" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="71564ca7-7845-92b3-7433-2f2beeb6b9f7" name="VerifyNonZeroLengthOnNonEmpty" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="b191e585-49d9-df8e-c156-307f798db169" name="AddAttributeRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="79c0d33a-f7f2-fd69-1b4d-57ee3ece2cca" name="EqualityTests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="77934ac4-bd65-7ad8-9c53-9c9447f9e175" name="GetReturnToArgumentAndNames" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="d6b120b7-fc16-6815-927e-af382cd44bbd" name="ReceivedInvalidSignature" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="29e45877-ca7a-85de-5c39-6d43befe1a1e" name="DiscoveryRequireSslWithInsecureXrdsButSecureLinkTags" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="0435e38a-71f2-d58d-9c07-d97d830a1578" name="ExtensionResponsesAreSigned" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ae1ef27c-fbfe-c57e-a1e0-c1ef9de4ea23" name="CommonProperties" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="384fecbf-f18e-edcb-a2eb-fb0322f031aa" name="ApplyHeadersToResponseNullListenerResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="f533bf9e-daa1-b26a-4789-372f3a9291d6" name="TryRequireSslAdjustsIdentifier" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="188ce83b-3117-adb5-4b89-12f2b09be1de" name="CtorSimpleConsumer" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="06ec5bce-5a78-89c3-0cda-fa8bddfea27d" name="SetCountZero" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="c4001e1c-75ad-236b-284f-318905d2bc3a" name="CreateRequestOnNonOpenID" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="efd570c9-5e74-17e4-f332-ac257c8e8aff" name="RealmReturnToMismatchV1" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -433,13 +433,13 @@ <TestLink id="a9f7897c-b299-807b-0612-384732cd10c9" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="456c3468-9081-4879-7e7e-8299bd8c7f68" name="IsReadOnly" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="0f36556d-ece7-eb70-8597-a9d085165c2c" name="Sign" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="47706bc6-7bee-0385-62b4-4f9cec6cc702" name="CtorWithTextMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="f69f1c0c-e258-95fb-4fcb-ad14bfc40e3c" name="Discover" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5435ab79-de25-e2fc-0b2d-b05d5686d27d" name="IsUrlWithinRealmTests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="dbf7855c-0cc6-309f-b5f5-022e0b95fe3b" name="QueryStringLookupWithoutQuery" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="93041654-1050-3878-6b90-656a7e2e3cfd" name="CtorDefault" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="10a8b8e5-e147-838c-0708-be98d5e4490e" name="CtorFull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="d6088ffe-ccf5-9738-131b-0fc1bc7e3707" name="TrimFragment" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="188ce83b-3117-adb5-4b89-12f2b09be1de" name="CtorSimpleConsumer" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="f4537b23-bb5e-5c6f-da53-64b34472f0dc" name="ChannelGetter" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="fdf5b3df-239b-26fd-c1a2-152057195b7e" name="ReadFromRequestForm" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="88aaa032-b18a-b334-937b-66837c5f987c" name="AssociateRenegotiateBitLength" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="c351c660-d583-d869-0129-2e312665d815" name="CtorBlank" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 5dad6f0..309b731 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -229,8 +229,10 @@ <Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" /> <Compile Include="Messaging\Reflection\IMessagePartNullEncoder.cs" /> <Compile Include="Messaging\Reflection\MessageDescriptionCollection.cs" /> + <Compile Include="OAuth\ChannelElements\ICombinedOpenIdProviderTokenManager.cs" /> <Compile Include="OAuth\ChannelElements\IConsumerDescription.cs" /> <Compile Include="OAuth\ChannelElements\IConsumerTokenManager.cs" /> + <Compile Include="OAuth\ChannelElements\IOpenIdOAuthTokenManager.cs" /> <Compile Include="OAuth\ChannelElements\IServiceProviderTokenManager.cs" /> <Compile Include="OAuth\ChannelElements\OAuthConsumerMessageFactory.cs" /> <Compile Include="OAuth\ChannelElements\ITokenGenerator.cs" /> @@ -343,6 +345,10 @@ <Compile Include="OpenId\Extensions\ExtensionBase.cs" /> <Compile Include="OpenId\Extensions\ExtensionArgumentsManager.cs" /> <Compile Include="OpenId\Extensions\IClientScriptExtensionResponse.cs" /> + <Compile Include="OpenId\Extensions\OAuth\AuthorizationRequest.cs" /> + <Compile Include="OpenId\Extensions\OAuth\AuthorizationApprovedResponse.cs" /> + <Compile Include="OpenId\Extensions\OAuth\Constants.cs" /> + <Compile Include="OpenId\Extensions\OAuth\AuthorizationDeclinedResponse.cs" /> <Compile Include="OpenId\Extensions\OpenIdExtensionFactoryAggregator.cs" /> <Compile Include="OpenId\Extensions\StandardOpenIdExtensionFactory.cs" /> <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\AuthenticationPolicies.cs" /> diff --git a/src/DotNetOpenAuth/GlobalSuppressions.cs b/src/DotNetOpenAuth/GlobalSuppressions.cs index c99fbfc..6997632 100644 --- a/src/DotNetOpenAuth/GlobalSuppressions.cs +++ b/src/DotNetOpenAuth/GlobalSuppressions.cs @@ -40,5 +40,6 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.OpenId.Messages")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "We sign it when producing drops.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.OpenId.Extensions.OAuth")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.Messaging.Reflection")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "oauthverifier", Scope = "resource", Target = "DotNetOpenAuth.OAuth.OAuthStrings.resources")] diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs index 2c6d677..a17f1e0 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs +++ b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs @@ -16,6 +16,8 @@ namespace DotNetOpenAuth.InfoCard { using System.Drawing.Design; using System.Globalization; using System.Linq; + using System.Text.RegularExpressions; + using System.Web; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; @@ -256,8 +258,27 @@ namespace DotNetOpenAuth.InfoCard { [Description("The URL to this site's privacy policy.")] [Category(InfoCardCategory), DefaultValue(PrivacyUrlDefault)] public string PrivacyUrl { - get { return (string)this.ViewState[PrivacyUrlViewStateKey] ?? PrivacyUrlDefault; } - set { this.ViewState[PrivacyUrlViewStateKey] = value; } + get { + return (string)this.ViewState[PrivacyUrlViewStateKey] ?? PrivacyUrlDefault; + } + + set { + if (this.Page != null && !this.DesignMode) { + // Validate new value by trying to construct a Uri based on it. + new Uri(new HttpRequestInfo(HttpContext.Current.Request).UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure. + } else { + // We can't fully test it, but it should start with either ~/ or a protocol. + if (Regex.IsMatch(value, @"^https?://")) { + new Uri(value); // make sure it's fully-qualified, but ignore wildcards + } else if (value.StartsWith("~/", StringComparison.Ordinal)) { + // this is valid too + } else { + throw new UriFormatException(); + } + } + + this.ViewState[PrivacyUrlViewStateKey] = value; + } } /// <summary> @@ -482,6 +503,20 @@ namespace DotNetOpenAuth.InfoCard { } /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnPreRender(EventArgs e) { + base.OnPreRender(e); + + if (!this.DesignMode) { + // The Cardspace selector will display an ugly error to the user if + // the privacy URL is present but the privacy version is not. + ErrorUtilities.VerifyOperation(string.IsNullOrEmpty(this.PrivacyUrl) || !string.IsNullOrEmpty(this.PrivacyVersion), InfoCardStrings.PrivacyVersionRequiredWithPrivacyUrl); + } + } + + /// <summary> /// Creates a control that renders to <Param Name="{0}" Value="{1}" /> /// </summary> /// <param name="name">The parameter name.</param> @@ -609,7 +644,8 @@ namespace DotNetOpenAuth.InfoCard { } if (!string.IsNullOrEmpty(this.PrivacyUrl)) { - cardSpaceControl.Controls.Add(CreateParam("privacyUrl", this.PrivacyUrl)); + string privacyUrl = this.DesignMode ? this.PrivacyUrl : new Uri(Page.Request.Url, Page.ResolveUrl(this.PrivacyUrl)).AbsoluteUri; + cardSpaceControl.Controls.Add(CreateParam("privacyUrl", privacyUrl)); } if (!string.IsNullOrEmpty(this.PrivacyVersion)) { diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs index 4b1dc60..00eb1af 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs +++ b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.3521 +// Runtime Version:2.0.50727.4918 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -97,6 +97,15 @@ namespace DotNetOpenAuth.InfoCard { } /// <summary> + /// Looks up a localized string similar to The PrivacyVersion property must be set whenever the PrivacyUrl property is set.. + /// </summary> + internal static string PrivacyVersionRequiredWithPrivacyUrl { + get { + return ResourceManager.GetString("PrivacyVersionRequiredWithPrivacyUrl", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Click here to select your Information Card.. /// </summary> internal static string SelectorClickPrompt { diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx index e82e8cd..956b321 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx +++ b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx @@ -129,6 +129,9 @@ <data name="PpidClaimRequired" xml:space="preserve"> <value>This operation requires the PPID claim to be included in the InfoCard token.</value> </data> + <data name="PrivacyVersionRequiredWithPrivacyUrl" xml:space="preserve"> + <value>The PrivacyVersion property must be set whenever the PrivacyUrl property is set.</value> + </data> <data name="SelectorClickPrompt" xml:space="preserve"> <value>Click here to select your Information Card.</value> </data> diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index 7435a90..2e0f1a8 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -394,6 +394,8 @@ namespace DotNetOpenAuth.Messaging { public TResponse Request<TResponse>(IDirectedProtocolMessage requestMessage) where TResponse : class, IProtocolMessage { Contract.Requires(requestMessage != null); + Contract.Ensures(Contract.Result<TResponse>() != null); + IProtocolMessage response = this.Request(requestMessage); ErrorUtilities.VerifyProtocol(response != null, MessagingStrings.ExpectedMessageNotReceived, typeof(TResponse)); @@ -565,13 +567,13 @@ namespace DotNetOpenAuth.Messaging { Contract.Requires(request != null); ErrorUtilities.VerifyArgumentNotNull(request, "request"); - Logger.Channel.DebugFormat("Incoming HTTP request: {0}", request.Url.AbsoluteUri); + Logger.Channel.DebugFormat("Incoming HTTP request: {0} {1}", request.HttpMethod, request.UrlBeforeRewriting.AbsoluteUri); // Search Form data first, and if nothing is there search the QueryString - Contract.Assume(request.Form != null && request.QueryString != null); + Contract.Assume(request.Form != null && request.QueryStringBeforeRewriting != null); var fields = request.Form.ToDictionary(); if (fields.Count == 0 && request.HttpMethod != "POST") { // OpenID 2.0 section 4.1.2 - fields = request.QueryString.ToDictionary(); + fields = request.QueryStringBeforeRewriting.ToDictionary(); } return (IDirectedProtocolMessage)this.Receive(fields, request.GetRecipient()); diff --git a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs index b1800d7..14666f2 100644 --- a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs +++ b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs @@ -10,6 +10,7 @@ namespace DotNetOpenAuth.Messaging { using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; + using System.Globalization; using System.IO; using System.Net; using System.ServiceModel.Channels; @@ -54,6 +55,7 @@ namespace DotNetOpenAuth.Messaging { this.HttpMethod = request.HttpMethod; this.Url = request.Url; + this.UrlBeforeRewriting = GetPublicFacingUrl(request); this.RawUrl = request.RawUrl; this.Headers = GetHeaderCollection(request.Headers); this.InputStream = request.InputStream; @@ -87,6 +89,7 @@ namespace DotNetOpenAuth.Messaging { this.HttpMethod = httpMethod; this.Url = requestUrl; + this.UrlBeforeRewriting = requestUrl; this.RawUrl = rawUrl; this.Headers = headers; this.InputStream = inputStream; @@ -102,6 +105,7 @@ namespace DotNetOpenAuth.Messaging { this.HttpMethod = listenerRequest.HttpMethod; this.Url = listenerRequest.Url; + this.UrlBeforeRewriting = listenerRequest.Url; this.RawUrl = listenerRequest.RawUrl; this.Headers = new WebHeaderCollection(); foreach (string key in listenerRequest.Headers) { @@ -125,6 +129,7 @@ namespace DotNetOpenAuth.Messaging { this.HttpMethod = request.Method; this.Headers = request.Headers; this.Url = requestUri; + this.UrlBeforeRewriting = requestUri; this.RawUrl = MakeUpRawUrlFromUrl(requestUri); } @@ -146,6 +151,7 @@ namespace DotNetOpenAuth.Messaging { this.HttpMethod = request.Method; this.Url = request.RequestUri; + this.UrlBeforeRewriting = request.RequestUri; this.RawUrl = MakeUpRawUrlFromUrl(request.RequestUri); this.Headers = GetHeaderCollection(request.Headers); this.InputStream = null; @@ -190,27 +196,13 @@ namespace DotNetOpenAuth.Messaging { internal string RawUrl { get; set; } /// <summary> - /// Gets the full URL of a request before rewriting. + /// Gets or sets the full public URL used by the remote client to initiate this request, + /// before any URL rewriting and before any changes made by web farm load distributors. /// </summary> - internal Uri UrlBeforeRewriting { - get { - if (this.Url == null || this.RawUrl == null) { - return null; - } - - // We use Request.Url for the full path to the server, and modify it - // with Request.RawUrl to capture both the cookieless session "directory" if it exists - // and the original path in case URL rewriting is going on. We don't want to be - // fooled by URL rewriting because we're comparing the actual URL with what's in - // the return_to parameter in some cases. - // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless - // session, but not the URL rewriting problem. - return new Uri(this.Url, this.RawUrl); - } - } + internal Uri UrlBeforeRewriting { get; set; } /// <summary> - /// Gets the query part of the URL (The ? and everything after it). + /// Gets the query part of the URL (The ? and everything after it), after URL rewriting. /// </summary> internal string Query { get { return this.Url != null ? this.Url.Query : null; } @@ -297,7 +289,7 @@ namespace DotNetOpenAuth.Messaging { /// <c>true</c> if this request's URL was rewritten; otherwise, <c>false</c>. /// </value> internal bool IsUrlRewritten { - get { return this.Url.PathAndQuery != this.RawUrl; } + get { return this.Url != this.UrlBeforeRewriting; } } /// <summary> @@ -316,6 +308,45 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Gets the public facing URL for the given incoming HTTP request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>The URI that the outside world used to create this request.</returns> + private static Uri GetPublicFacingUrl(HttpRequest request) { + Contract.Requires(request != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + // Due to URL rewriting, cloud computing (i.e. Azure) + // and web farms, etc., we have to be VERY careful about what + // we consider the incoming URL. We want to see the URL as it would + // appear on the public-facing side of the hosting web site. + // HttpRequest.Url gives us the internal URL in a cloud environment, + // So we use a variable that (at least from what I can tell) gives us + // the public URL: + if (request.ServerVariables["HTTP_HOST"] != null) { + ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols."); + UriBuilder publicRequestUri = new UriBuilder(request.Url); + Uri hostAndPort = new Uri(request.Url.Scheme + Uri.SchemeDelimiter + request.ServerVariables["HTTP_HOST"]); + publicRequestUri.Host = hostAndPort.Host; + publicRequestUri.Port = hostAndPort.Port; + if (request.ServerVariables["HTTP_X_FORWARDED_PROTO"] != null) { + publicRequestUri.Scheme = request.ServerVariables["HTTP_X_FORWARDED_PROTO"]; + } + return publicRequestUri.Uri; + } else { + // Failover to the method that works for non-web farm enviroments. + // We use Request.Url for the full path to the server, and modify it + // with Request.RawUrl to capture both the cookieless session "directory" if it exists + // and the original path in case URL rewriting is going on. We don't want to be + // fooled by URL rewriting because we're comparing the actual URL with what's in + // the return_to parameter in some cases. + // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless + // session, but not the URL rewriting problem. + return new Uri(request.Url, request.RawUrl); + } + } + + /// <summary> /// Makes up a reasonable guess at the raw URL from the possibly rewritten URL. /// </summary> /// <param name="url">A full URL.</param> diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index 1cb2177..463b556 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -577,7 +577,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The request to get recipient information from.</param> /// <returns>The recipient.</returns> internal static MessageReceivingEndpoint GetRecipient(this HttpRequestInfo request) { - return new MessageReceivingEndpoint(request.Url, request.HttpMethod == "GET" ? HttpDeliveryMethods.GetRequest : HttpDeliveryMethods.PostRequest); + return new MessageReceivingEndpoint(request.UrlBeforeRewriting, request.HttpMethod == "GET" ? HttpDeliveryMethods.GetRequest : HttpDeliveryMethods.PostRequest); } /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs index 18e8aa2..0b5b6d0 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs @@ -257,7 +257,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { MessagePart part; if (this.description.Mapping.TryGetValue(key, out part)) { value = part.GetValue(this.message); - return true; + return value != null; } return this.message.ExtraData.TryGetValue(key, out value); } @@ -277,13 +277,28 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <summary> /// Removes all values in the message. /// </summary> - public void Clear() { + public void ClearValues() { foreach (string key in this.Keys) { this.Remove(key); } } /// <summary> + /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </summary> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. + /// </exception> + /// <remarks> + /// This method cannot be implemented because keys are not guaranteed to be removed + /// since some are inherent to the type of message that this dictionary provides + /// access to. + /// </remarks> + public void Clear() { + throw new NotSupportedException(); + } + + /// <summary> /// Checks whether a named value has been set on the message. /// </summary> /// <param name="item">The name/value pair.</param> diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs index 0732bb2..a8f04ec 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs @@ -188,7 +188,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { this.field.SetValue(message, this.ToValue(value)); } } - } catch (FormatException ex) { + } catch (Exception ex) { throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartReadFailure, message.GetType(), this.Name, value); } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs new file mode 100644 index 0000000..ff004cb --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------- +// <copyright file="ICombinedOpenIdProviderTokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using DotNetOpenAuth.OpenId; + + /// <summary> + /// An interface that providers that play a dual role as OpenID Provider + /// and OAuth Service Provider should implement on their token manager classes. + /// </summary> + /// <remarks> + /// This interface should be implemented by the same class that implements + /// <see cref="ITokenManager"/> in order to enable the OpenID+OAuth extension. + /// </remarks> + public interface ICombinedOpenIdProviderTokenManager { + /// <summary> + /// Gets the OAuth consumer key for a given OpenID relying party realm. + /// </summary> + /// <param name="realm">The relying party's OpenID realm.</param> + /// <returns>The OAuth consumer key for a given OpenID realm.</returns> + /// <para>This is a security-critical function. Since OpenID requests + /// and OAuth extensions for those requests can be formulated by ANYONE + /// (no signing is required by the relying party), and since the response to + /// the authentication will include access the user is granted to the + /// relying party who CLAIMS to be from some realm, it is of paramount + /// importance that the realm is recognized as belonging to the consumer + /// key by the host service provider in order to protect against phishers.</para> + string GetConsumerKey(Realm realm); + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs new file mode 100644 index 0000000..b3ee320 --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// <copyright file="IOpenIdOAuthTokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions.OAuth; + + /// <summary> + /// Additional methods an <see cref="ITokenManager"/> implementing class + /// may implement to support the OpenID+OAuth extension. + /// </summary> + public interface IOpenIdOAuthTokenManager { + /// <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> + void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization); + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs index ce4c610..d325825 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs @@ -163,7 +163,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { // Add receiving HTTP transport information required for signature generation. var signedMessage = message as ITamperResistantOAuthMessage; if (signedMessage != null) { - signedMessage.Recipient = request.Url; + signedMessage.Recipient = request.UrlBeforeRewriting; signedMessage.HttpMethod = request.HttpMethod; } diff --git a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs index 8c4484b..55b40ac 100644 --- a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs +++ b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs @@ -233,6 +233,10 @@ namespace DotNetOpenAuth.OAuth { /// The access token assigned by the Service Provider. /// </returns> protected AuthorizedTokenResponse ProcessUserAuthorization(string requestToken, string verifier) { + Contract.Requires(!String.IsNullOrEmpty(requestToken)); + Contract.Ensures(Contract.Result<AuthorizedTokenResponse>() != null); + ErrorUtilities.VerifyNonZeroLength(requestToken, "requestToken"); + var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, this.ServiceProvider.Version) { RequestToken = requestToken, VerificationCode = verifier, diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs index 689998a..3593446 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs +++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs @@ -115,6 +115,24 @@ namespace DotNetOpenAuth.OAuth { } /// <summary> + /// Looks up a localized string similar to Use of the OpenID+OAuth extension requires that the token manager in use implement the {0} interface.. + /// </summary> + internal static string OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface { + get { + return ResourceManager.GetString("OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The OpenID Relying Party's realm is not recognized as belonging to the OAuth Consumer identified by the consumer key given.. + /// </summary> + internal static string OpenIdOAuthRealmConsumerKeyDoNotMatch { + get { + return ResourceManager.GetString("OpenIdOAuthRealmConsumerKeyDoNotMatch", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to This OAuth service provider requires OAuth consumers to implement OAuth {0}, but this consumer appears to only support {1}.. /// </summary> internal static string MinimumConsumerVersionRequirementNotMet { diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx index a40b35d..bbeeda9 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx +++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx @@ -138,6 +138,12 @@ <data name="MinimumConsumerVersionRequirementNotMet" xml:space="preserve"> <value>This OAuth service provider requires OAuth consumers to implement OAuth {0}, but this consumer appears to only support {1}.</value> </data> + <data name="OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface" xml:space="preserve"> + <value>Use of the OpenID+OAuth extension requires that the token manager in use implement the {0} interface.</value> + </data> + <data name="OpenIdOAuthRealmConsumerKeyDoNotMatch" xml:space="preserve"> + <value>The OpenID Relying Party's realm is not recognized as belonging to the OAuth Consumer identified by the consumer key given.</value> + </data> <data name="RequestUrlMustNotHaveOAuthParameters" xml:space="preserve"> <value>The request URL query MUST NOT contain any OAuth Protocol Parameters.</value> </data> diff --git a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs index f9c4e4e..4563c22 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs @@ -17,6 +17,10 @@ namespace DotNetOpenAuth.OAuth { using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions.OAuth; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; /// <summary> /// A web application that allows access via OAuth. @@ -251,6 +255,73 @@ namespace DotNetOpenAuth.OAuth { } /// <summary> + /// Gets the OAuth authorization request included with an OpenID authentication + /// request. + /// </summary> + /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> + /// <returns> + /// The scope of access the relying party is requesting. + /// </returns> + /// <remarks> + /// <para>Call this method rather than simply extracting the OAuth extension + /// out from the authentication request directly to ensure that the additional + /// security measures that are required are taken.</para> + /// </remarks> + public AuthorizationRequest ReadAuthorizationRequest(IAuthenticationRequest openIdAuthenticationRequest) { + Contract.Requires(openIdAuthenticationRequest != null); + Contract.Requires(this.TokenManager is ICombinedOpenIdProviderTokenManager); + ErrorUtilities.VerifyArgumentNotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); + var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager; + ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); + + var authzRequest = openIdAuthenticationRequest.GetExtension<AuthorizationRequest>(); + if (authzRequest == null) { + return null; + } + + // OpenID+OAuth spec section 9: + // The Combined Provider SHOULD verify that the consumer key passed in the + // request is authorized to be used for the realm passed in the request. + string expectedConsumerKey = openidTokenManager.GetConsumerKey(openIdAuthenticationRequest.Realm); + ErrorUtilities.VerifyProtocol( + string.Equals(expectedConsumerKey, authzRequest.Consumer, StringComparison.Ordinal), + OAuthStrings.OpenIdOAuthRealmConsumerKeyDoNotMatch); + + return authzRequest; + } + + /// <summary> + /// Attaches the authorization response to an OpenID authentication response. + /// </summary> + /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> + /// <param name="consumerKey">The consumer key. May and should be <c>null</c> if and only if <paramref name="scope"/> is null.</param> + /// <param name="scope">The approved access scope. Use <c>null</c> to indicate no access was granted. The empty string will be interpreted as some default level of access is granted.</param> + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We want to take IAuthenticationRequest because that's the only supported use case.")] + public void AttachAuthorizationResponse(IAuthenticationRequest openIdAuthenticationRequest, string consumerKey, string scope) { + Contract.Requires(openIdAuthenticationRequest != null); + Contract.Requires((consumerKey == null) == (scope == null)); + Contract.Requires(this.TokenManager is IOpenIdOAuthTokenManager); + ErrorUtilities.VerifyArgumentNotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); + var openidTokenManager = this.TokenManager as IOpenIdOAuthTokenManager; + ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); + + IOpenIdMessageExtension response; + if (scope != null) { + // Generate an authorized request token to return to the relying party. + var approvedResponse = new AuthorizationApprovedResponse { + RequestToken = this.TokenGenerator.GenerateRequestToken(consumerKey), + Scope = scope, + }; + openidTokenManager.StoreOpenIdAuthorizedRequestToken(consumerKey, approvedResponse); + response = approvedResponse; + } else { + response = new AuthorizationDeclinedResponse(); + } + + openIdAuthenticationRequest.AddResponseExtension(response); + } + + /// <summary> /// Prepares the message to send back to the consumer following proper authorization of /// a token by an interactive user at the Service Provider's web site. /// </summary> diff --git a/src/DotNetOpenAuth/OAuth/WebConsumer.cs b/src/DotNetOpenAuth/OAuth/WebConsumer.cs index 11f3da8..56d3029 100644 --- a/src/DotNetOpenAuth/OAuth/WebConsumer.cs +++ b/src/DotNetOpenAuth/OAuth/WebConsumer.cs @@ -7,10 +7,13 @@ namespace DotNetOpenAuth.OAuth { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.OpenId.Extensions.OAuth; + using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> /// A website or application that uses OAuth to access the Service Provider on behalf of the User. @@ -71,11 +74,76 @@ namespace DotNetOpenAuth.OAuth { } /// <summary> + /// Attaches an OAuth authorization request to an outgoing OpenID authentication request. + /// </summary> + /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> + /// <param name="scope">The scope of access that is requested of the service provider.</param> + public void AttachAuthorizationRequest(IAuthenticationRequest openIdAuthenticationRequest, string scope) { + Contract.Requires(openIdAuthenticationRequest != null); + ErrorUtilities.VerifyArgumentNotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); + + var authorizationRequest = new AuthorizationRequest { + Consumer = this.ConsumerKey, + Scope = scope, + }; + + openIdAuthenticationRequest.AddExtension(authorizationRequest); + } + + /// <summary> + /// Processes an incoming authorization-granted message from an SP and obtains an access token. + /// </summary> + /// <param name="openIdAuthenticationResponse">The OpenID authentication response that may be carrying an authorized request token.</param> + /// <returns> + /// The access token, or null if OAuth authorization was denied by the user or service provider. + /// </returns> + /// <remarks> + /// The access token, if granted, is automatically stored in the <see cref="ConsumerBase.TokenManager"/>. + /// The token manager instance must implement <see cref="IOpenIdOAuthTokenManager"/>. + /// </remarks> + public AuthorizedTokenResponse ProcessUserAuthorization(IAuthenticationResponse openIdAuthenticationResponse) { + Contract.Requires(openIdAuthenticationResponse != null); + Contract.Requires(this.TokenManager is IOpenIdOAuthTokenManager); + ErrorUtilities.VerifyArgumentNotNull(openIdAuthenticationResponse, "openIdAuthenticationResponse"); + var openidTokenManager = this.TokenManager as IOpenIdOAuthTokenManager; + ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); + + // The OAuth extension is only expected in positive assertion responses. + if (openIdAuthenticationResponse.Status != AuthenticationStatus.Authenticated) { + return null; + } + + // Retrieve the OAuth extension + var positiveAuthorization = openIdAuthenticationResponse.GetExtension<AuthorizationApprovedResponse>(); + if (positiveAuthorization == null) { + return null; + } + + // Prepare a message to exchange the request token for an access token. + var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, this.ServiceProvider.Version) { + RequestToken = positiveAuthorization.RequestToken, + ConsumerKey = this.ConsumerKey, + }; + + // Retrieve the access token and store it in the token manager. + openidTokenManager.StoreOpenIdAuthorizedRequestToken(this.ConsumerKey, positiveAuthorization); + var grantAccess = this.Channel.Request<AuthorizedTokenResponse>(requestAccess); + this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, positiveAuthorization.RequestToken, grantAccess.AccessToken, grantAccess.TokenSecret); + + // Provide the caller with the access token so it may be associated with the user + // that is logging in. + return grantAccess; + } + + /// <summary> /// Processes an incoming authorization-granted message from an SP and obtains an access token. /// </summary> /// <param name="request">The incoming HTTP request.</param> /// <returns>The access token, or null if no incoming authorization message was recognized.</returns> - internal AuthorizedTokenResponse ProcessUserAuthorization(HttpRequestInfo request) { + public AuthorizedTokenResponse ProcessUserAuthorization(HttpRequestInfo request) { + Contract.Requires(request != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + UserAuthorizationResponse authorizationMessage; if (this.Channel.TryReadFromRequest<UserAuthorizationResponse>(request, out authorizationMessage)) { string requestToken = authorizationMessage.RequestToken; diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs index 035f389..a352c76 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs @@ -23,39 +23,13 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// </summary> internal class ExtensionsBindingElement : IChannelBindingElement { /// <summary> - /// The security settings on the Relying Party that is hosting this binding element. - /// </summary> - private RelyingPartySecuritySettings rpSecuritySettings; - - /// <summary> - /// The security settings on the Provider that is hosting this binding element. - /// </summary> - private ProviderSecuritySettings opSecuritySettings; - - /// <summary> /// Initializes a new instance of the <see cref="ExtensionsBindingElement"/> class. /// </summary> /// <param name="extensionFactory">The extension factory.</param> - /// <param name="securitySettings">The security settings to apply.</param> - internal ExtensionsBindingElement(IOpenIdExtensionFactory extensionFactory, RelyingPartySecuritySettings securitySettings) { + internal ExtensionsBindingElement(IOpenIdExtensionFactory extensionFactory) { ErrorUtilities.VerifyArgumentNotNull(extensionFactory, "extensionFactory"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); this.ExtensionFactory = extensionFactory; - this.rpSecuritySettings = securitySettings; - } - - /// <summary> - /// Initializes a new instance of the <see cref="ExtensionsBindingElement"/> class. - /// </summary> - /// <param name="extensionFactory">The extension factory.</param> - /// <param name="securitySettings">The security settings to apply.</param> - internal ExtensionsBindingElement(IOpenIdExtensionFactory extensionFactory, ProviderSecuritySettings securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(extensionFactory, "extensionFactory"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); - - this.ExtensionFactory = extensionFactory; - this.opSecuritySettings = securitySettings; } #region IChannelBindingElement Members @@ -159,16 +133,56 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { var extendableMessage = message as IProtocolMessageWithExtensions; if (extendableMessage != null) { - // We have a helper class that will do all the heavy-lifting of organizing - // all the extensions, their aliases, and their parameters. - bool isAtProvider = extendableMessage is SignedResponseRequest; - var extensionManager = ExtensionArgumentsManager.CreateIncomingExtensions(this.GetExtensionsDictionary(message)); - foreach (string typeUri in extensionManager.GetExtensionTypeUris()) { - var extensionData = extensionManager.GetExtensionArguments(typeUri); + // First add the extensions that are signed by the Provider. + foreach (IOpenIdMessageExtension signedExtension in this.GetExtensions(extendableMessage, true, null)) { + signedExtension.IsSignedByRemoteParty = true; + extendableMessage.Extensions.Add(signedExtension); + } - // Initialize this particular extension. - IOpenIdMessageExtension extension = this.ExtensionFactory.Create(typeUri, extensionData, extendableMessage, isAtProvider); - if (extension != null) { + // Now search again, considering ALL extensions whether they are signed or not, + // skipping the signed ones and adding the new ones as unsigned extensions. + Func<string, bool> isNotSigned = typeUri => !extendableMessage.Extensions.Cast<IOpenIdMessageExtension>().Any(ext => ext.TypeUri == typeUri); + foreach (IOpenIdMessageExtension unsignedExtension in this.GetExtensions(extendableMessage, false, isNotSigned)) { + unsignedExtension.IsSignedByRemoteParty = false; + extendableMessage.Extensions.Add(unsignedExtension); + } + + return MessageProtections.None; + } + + return null; + } + + #endregion + + /// <summary> + /// Gets the extensions on a message. + /// </summary> + /// <param name="message">The carrier of the extensions.</param> + /// <param name="ignoreUnsigned">If set to <c>true</c> only signed extensions will be available.</param> + /// <param name="extensionFilter">A optional filter that takes an extension type URI and + /// returns a value indicating whether that extension should be deserialized and + /// returned in the sequence. May be null.</param> + /// <returns>A sequence of extensions in the message.</returns> + private IEnumerable<IOpenIdMessageExtension> GetExtensions(IProtocolMessageWithExtensions message, bool ignoreUnsigned, Func<string, bool> extensionFilter) { + bool isAtProvider = message is SignedResponseRequest; + + // We have a helper class that will do all the heavy-lifting of organizing + // all the extensions, their aliases, and their parameters. + var extensionManager = ExtensionArgumentsManager.CreateIncomingExtensions(this.GetExtensionsDictionary(message, ignoreUnsigned)); + foreach (string typeUri in extensionManager.GetExtensionTypeUris()) { + // Our caller may have already obtained a signed version of this extension, + // so skip it if they don't want this one. + if (extensionFilter != null && !extensionFilter(typeUri)) { + continue; + } + + var extensionData = extensionManager.GetExtensionArguments(typeUri); + + // Initialize this particular extension. + IOpenIdMessageExtension extension = this.ExtensionFactory.Create(typeUri, extensionData, message, isAtProvider); + if (extension != null) { + try { MessageDictionary extensionDictionary = this.Channel.MessageDescriptions.GetAccessor(extension); foreach (var pair in extensionData) { extensionDictionary[pair.Key] = pair.Value; @@ -179,39 +193,34 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { if (customSerializingExtension != null) { customSerializingExtension.OnReceiving(); } + } catch (ProtocolException ex) { + Logger.OpenId.ErrorFormat(OpenIdStrings.BadExtension, extension.GetType(), ex); + extension = null; + } - extendableMessage.Extensions.Add(extension); - } else { - Logger.OpenId.WarnFormat("Extension with type URI '{0}' ignored because it is not a recognized extension.", typeUri); + if (extension != null) { + yield return extension; } + } else { + Logger.OpenId.WarnFormat("Extension with type URI '{0}' ignored because it is not a recognized extension.", typeUri); } - - return MessageProtections.None; } - - return null; } - #endregion - /// <summary> /// Gets the dictionary of message parts that should be deserialized into extensions. /// </summary> /// <param name="message">The message.</param> - /// <returns>A dictionary of message parts, including only signed parts when appropriate.</returns> - private IDictionary<string, string> GetExtensionsDictionary(IProtocolMessage message) { + /// <param name="ignoreUnsigned">If set to <c>true</c> only signed extensions will be available.</param> + /// <returns> + /// A dictionary of message parts, including only signed parts when appropriate. + /// </returns> + private IDictionary<string, string> GetExtensionsDictionary(IProtocolMessage message, bool ignoreUnsigned) { Contract.Requires(this.Channel != null); ErrorUtilities.VerifyOperation(this.Channel != null, "Channel property has not been set."); - // An IndirectSignedResponse message (the only one we care to filter parts for) - // can be received both by RPs and OPs (during check_auth). - // Whichever party is reading the extensions, apply their security policy regarding - // signing. (Although OPs have no reason to deserialize extensions during check_auth) - // so that scenario might be optimized away eventually. - bool extensionsShouldBeSigned = this.rpSecuritySettings != null ? !this.rpSecuritySettings.AllowUnsignedIncomingExtensions : this.opSecuritySettings.SignOutgoingExtensions; - IndirectSignedResponse signedResponse = message as IndirectSignedResponse; - if (signedResponse != null && extensionsShouldBeSigned) { + if (signedResponse != null && ignoreUnsigned) { return signedResponse.GetSignedMessageParts(this.Channel); } else { return this.Channel.MessageDescriptions.GetAccessor(message); diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs index 1ddd1eb..bc86259 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs @@ -341,8 +341,8 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration(); List<IChannelBindingElement> elements = new List<IChannelBindingElement>(7); + elements.Add(new ExtensionsBindingElement(extensionFactory)); if (isRelyingPartyRole) { - elements.Add(new ExtensionsBindingElement(extensionFactory, rpSecuritySettings)); elements.Add(new BackwardCompatibilityBindingElement()); if (associationStore != null) { @@ -358,8 +358,6 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { elements.Add(new ReturnToSignatureBindingElement(rpAssociationStore, rpSecuritySettings)); } } else { - elements.Add(new ExtensionsBindingElement(extensionFactory, opSecuritySettings)); - // Providers must always have a nonce store. ErrorUtilities.VerifyArgumentNotNull(nonceStore, "nonceStore"); } diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/FetchResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/FetchResponse.cs index 73039d4..9413e2f 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/FetchResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/FetchResponse.cs @@ -71,6 +71,16 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { public Uri UpdateUrl { get; set; } /// <summary> + /// Gets a value indicating whether this extension is signed by the Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>. + /// </value> + public bool IsSignedByProvider { + get { return this.IsSignedByRemoteParty; } + } + + /// <summary> /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>. /// </summary> /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param> diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/StoreResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/StoreResponse.cs index ae6ea5b..ba7f091 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/StoreResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/StoreResponse.cs @@ -74,6 +74,16 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { public string FailureReason { get; set; } /// <summary> + /// Gets a value indicating whether this extension is signed by the Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>. + /// </value> + public bool IsSignedByProvider { + get { return this.IsSignedByRemoteParty; } + } + + /// <summary> /// Gets or sets the mode argument. /// </summary> /// <value>One of 'store_response_success' or 'store_response_failure'.</value> diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionBase.cs b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionBase.cs index 3ca979d..108ac52 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionBase.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionBase.cs @@ -72,6 +72,18 @@ namespace DotNetOpenAuth.OpenId.Extensions { get { return this.AdditionalSupportedTypeUris; } } + /// <summary> + /// Gets or sets a value indicating whether this extension was + /// signed by the OpenID Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the provider; otherwise, <c>false</c>. + /// </value> + bool IOpenIdMessageExtension.IsSignedByRemoteParty { + get { return this.IsSignedByRemoteParty; } + set { this.IsSignedByRemoteParty = value; } + } + #endregion #region IMessage Properties @@ -101,6 +113,15 @@ namespace DotNetOpenAuth.OpenId.Extensions { } /// <summary> + /// Gets or sets a value indicating whether this extension was + /// signed by the OpenID Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the provider; otherwise, <c>false</c>. + /// </value> + protected bool IsSignedByRemoteParty { get; set; } + + /// <summary> /// Gets the additional TypeURIs that are supported by this extension, in preferred order. /// May be empty if none other than <see cref="IOpenIdMessageExtension.TypeUri"/> is supported, but /// should not be null. diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs new file mode 100644 index 0000000..5e7bc49 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthorizationApprovedResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.OAuth { + using System; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The OAuth response that a Provider may include with a positive + /// OpenID identity assertion with an approved request token. + /// </summary> + [Serializable] + public class AuthorizationApprovedResponse : ExtensionBase { + /// <summary> + /// The factory method that may be used in deserialization of this message. + /// </summary> + internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { + if (typeUri == Constants.TypeUri && !isProviderRole && data.ContainsKey(Constants.RequestTokenParameter)) { + return new AuthorizationApprovedResponse(); + } + + return null; + }; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthorizationApprovedResponse"/> class. + /// </summary> + public AuthorizationApprovedResponse() + : base(new Version(1, 0), Constants.TypeUri, null) { + } + + /// <summary> + /// Gets or sets the user-approved request token. + /// </summary> + /// <value>The request token.</value> + [MessagePart(Constants.RequestTokenParameter, IsRequired = true, AllowEmpty = false)] + public string RequestToken { get; set; } + + /// <summary> + /// Gets or sets a string that encodes, in a way possibly specific to the Combined Provider, one or more scopes that the returned request token is valid for. This will typically indicate a subset of the scopes requested in Section 8. + /// </summary> + [MessagePart("scope", IsRequired = false, AllowEmpty = true)] + public string Scope { get; set; } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs new file mode 100644 index 0000000..7c3a5ad --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthorizationDeclinedResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.OAuth { + using System; + + /// <summary> + /// The OAuth response that a Provider should include with a positive + /// OpenID identity assertion when OAuth authorization was declined. + /// </summary> + [Serializable] + public class AuthorizationDeclinedResponse : ExtensionBase { + /// <summary> + /// The factory method that may be used in deserialization of this message. + /// </summary> + internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { + if (typeUri == Constants.TypeUri && !isProviderRole && !data.ContainsKey(Constants.RequestTokenParameter)) { + return new AuthorizationDeclinedResponse(); + } + + return null; + }; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthorizationDeclinedResponse"/> class. + /// </summary> + public AuthorizationDeclinedResponse() + : base(new Version(1, 0), Constants.TypeUri, null) { + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationRequest.cs new file mode 100644 index 0000000..99f0880 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationRequest.cs @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthorizationRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.OAuth { + using System; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An extension to include with an authentication request in order to also + /// obtain authorization to access user data at the combined OpenID Provider + /// and Service Provider. + /// </summary> + /// <remarks> + /// <para>When requesting OpenID Authentication via the protocol mode "checkid_setup" + /// or "checkid_immediate", this extension can be used to request that the end + /// user authorize an OAuth access token at the same time as an OpenID + /// authentication. This is done by sending the following parameters as part + /// of the OpenID request. (Note that the use of "oauth" as part of the parameter + /// names here and in subsequent sections is just an example. See Section 5 for details.)</para> + /// <para>See section 8.</para> + /// </remarks> + [Serializable] + public class AuthorizationRequest : ExtensionBase { + /// <summary> + /// The factory method that may be used in deserialization of this message. + /// </summary> + internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { + if (typeUri == Constants.TypeUri && isProviderRole) { + return new AuthorizationRequest(); + } + + return null; + }; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthorizationRequest"/> class. + /// </summary> + public AuthorizationRequest() + : base(new Version(1, 0), Constants.TypeUri, null) { + } + + /// <summary> + /// Gets or sets the consumer key agreed upon between the Consumer and Service Provider. + /// </summary> + [MessagePart("consumer", IsRequired = true, AllowEmpty = false)] + public string Consumer { get; set; } + + /// <summary> + /// Gets or sets a string that encodes, in a way possibly specific to the Combined Provider, one or more scopes for the OAuth token expected in the authentication response. + /// </summary> + [MessagePart("scope", IsRequired = false)] + public string Scope { get; set; } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/Constants.cs b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/Constants.cs new file mode 100644 index 0000000..32efee9 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/Constants.cs @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------- +// <copyright file="Constants.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.OAuth { + /// <summary> + /// Constants used in the OpenID OAuth extension. + /// </summary> + internal static class Constants { + /// <summary> + /// The TypeURI for the OpenID OAuth extension. + /// </summary> + internal const string TypeUri = "http://specs.openid.net/extensions/oauth/1.0"; + + /// <summary> + /// The name of the parameter that carries the request token in the response. + /// </summary> + internal const string RequestTokenParameter = "request_token"; + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs index 8cf493c..b476cf7 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs @@ -140,6 +140,16 @@ namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { /// </remarks> public IDictionary<string, string> AssuranceLevels { get; private set; } + /// <summary> + /// Gets a value indicating whether this extension is signed by the Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>. + /// </value> + public bool IsSignedByProvider { + get { return this.IsSignedByRemoteParty; } + } + #region IMessageWithEvents Members /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs index f17df46..a58c754 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs @@ -217,6 +217,16 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { } /// <summary> + /// Gets a value indicating whether this extension is signed by the Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>. + /// </value> + public bool IsSignedByProvider { + get { return this.IsSignedByRemoteParty; } + } + + /// <summary> /// Tests equality of two <see cref="ClaimsResponse"/> objects. /// </summary> /// <param name="one">One instance to compare.</param> diff --git a/src/DotNetOpenAuth/OpenId/Extensions/StandardOpenIdExtensionFactory.cs b/src/DotNetOpenAuth/OpenId/Extensions/StandardOpenIdExtensionFactory.cs index 9dda6ad..a669672 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/StandardOpenIdExtensionFactory.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/StandardOpenIdExtensionFactory.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.ChannelElements; using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.OAuth; using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.Messages; @@ -35,6 +36,9 @@ namespace DotNetOpenAuth.OpenId.Extensions { this.RegisterExtension(StoreResponse.Factory); this.RegisterExtension(PolicyRequest.Factory); this.RegisterExtension(PolicyResponse.Factory); + this.RegisterExtension(AuthorizationRequest.Factory); + this.RegisterExtension(AuthorizationApprovedResponse.Factory); + this.RegisterExtension(AuthorizationDeclinedResponse.Factory); } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs b/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs index 2b205d8..d44809f 100644 --- a/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs +++ b/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs @@ -156,7 +156,7 @@ namespace DotNetOpenAuth.OpenId.Interop { /// <returns>The Provider's response to a previous authentication request, or null if no response is present.</returns> public AuthenticationResponseShim ProcessAuthentication(string url, string form) { OpenIdRelyingParty rp = new OpenIdRelyingParty(null); - HttpRequestInfo requestInfo = new HttpRequestInfo { Url = new Uri(url) }; + HttpRequestInfo requestInfo = new HttpRequestInfo { UrlBeforeRewriting = new Uri(url) }; if (!string.IsNullOrEmpty(form)) { requestInfo.HttpMethod = "POST"; requestInfo.InputStream = new MemoryStream(Encoding.Unicode.GetBytes(form)); diff --git a/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs b/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs index fb984fe..95080e6 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs @@ -41,5 +41,14 @@ namespace DotNetOpenAuth.OpenId.Messages { /// The <see cref="Extensions.SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example. /// </remarks> IEnumerable<string> AdditionalSupportedTypeUris { get; } + + /// <summary> + /// Gets or sets a value indicating whether this extension was + /// signed by the sender. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the sender; otherwise, <c>false</c>. + /// </value> + bool IsSignedByRemoteParty { get; set; } } } diff --git a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs index 107046d..9462d21 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs @@ -238,6 +238,20 @@ namespace DotNetOpenAuth.OpenId.Messages { internal bool ReturnToParametersSignatureValidated { get; set; } /// <summary> + /// Gets the signed extensions on this message. + /// </summary> + internal IEnumerable<IOpenIdMessageExtension> SignedExtensions { + get { return this.extensions.OfType<IOpenIdMessageExtension>().Where(ext => ext.IsSignedByRemoteParty); } + } + + /// <summary> + /// Gets the unsigned extensions on this message. + /// </summary> + internal IEnumerable<IOpenIdMessageExtension> UnsignedExtensions { + get { return this.extensions.OfType<IOpenIdMessageExtension>().Where(ext => !ext.IsSignedByRemoteParty); } + } + + /// <summary> /// Gets or sets the nonce that will protect the message from replay attacks. /// </summary> /// <value> diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs index 4c44439..134e7dd 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs @@ -133,6 +133,15 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Looks up a localized string similar to The {0} extension failed to deserialize and will be skipped. {1}. + /// </summary> + internal static string BadExtension { + get { + return ResourceManager.GetString("BadExtension", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Callback arguments are only supported when a {0} is provided to the {1}.. /// </summary> internal static string CallbackArgumentsRequireSecretStore { @@ -322,7 +331,7 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Looks up a localized string similar to The OpenId Provider issued an assertion for an Identifier whose discovery information did not match. + /// Looks up a localized string similar to The OpenID Provider issued an assertion for an Identifier whose discovery information did not match. ///Assertion endpoint info: ///{0} ///Discovered endpoint info: @@ -389,7 +398,7 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Looks up a localized string similar to No XRDS document containing OpenId relying party endpoint information could be found at {0}.. + /// Looks up a localized string similar to No XRDS document containing OpenID relying party endpoint information could be found at {0}.. /// </summary> internal static string NoRelyingPartyEndpointDiscovered { get { @@ -416,7 +425,7 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Looks up a localized string similar to No OpenId endpoint found.. + /// Looks up a localized string similar to No OpenID endpoint found.. /// </summary> internal static string OpenIdEndpointNotFound { get { @@ -425,7 +434,7 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Looks up a localized string similar to No OpenId url is provided.. + /// Looks up a localized string similar to No OpenID url is provided.. /// </summary> internal static string OpenIdTextBoxEmpty { get { diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx index 0cc193b..9c2ad9e 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx @@ -181,7 +181,7 @@ <value>Not a recognized XRI format: '{0}'.</value> </data> <data name="IssuedAssertionFailsIdentifierDiscovery" xml:space="preserve"> - <value>The OpenId Provider issued an assertion for an Identifier whose discovery information did not match. + <value>The OpenID Provider issued an assertion for an Identifier whose discovery information did not match. Assertion endpoint info: {0} Discovered endpoint info: @@ -206,7 +206,7 @@ Discovered endpoint info: <value>Diffie-Hellman session type '{0}' not found for OpenID {1}.</value> </data> <data name="OpenIdEndpointNotFound" xml:space="preserve"> - <value>No OpenId endpoint found.</value> + <value>No OpenID endpoint found.</value> </data> <data name="OperationOnlyValidForSetupRequiredState" xml:space="preserve"> <value>This operation is only allowed when IAuthenticationResponse.State == AuthenticationStatus.SetupRequired.</value> @@ -266,7 +266,7 @@ Discovered endpoint info: <value>An authentication request has already been created using CreateRequest().</value> </data> <data name="OpenIdTextBoxEmpty" xml:space="preserve"> - <value>No OpenId url is provided.</value> + <value>No OpenID url is provided.</value> </data> <data name="ClientScriptExtensionPropertyNameCollision" xml:space="preserve"> <value>An extension with this property name ('{0}') has already been registered.</value> @@ -281,7 +281,7 @@ Discovered endpoint info: <value>This operation is not supported by serialized authentication responses. Try this operation from the LoggedIn event handler.</value> </data> <data name="NoRelyingPartyEndpointDiscovered" xml:space="preserve"> - <value>No XRDS document containing OpenId relying party endpoint information could be found at {0}.</value> + <value>No XRDS document containing OpenID relying party endpoint information could be found at {0}.</value> </data> <data name="AbsoluteUriRequired" xml:space="preserve"> <value>An absolute URI is required for this value.</value> @@ -304,7 +304,10 @@ Discovered endpoint info: <data name="RequireSslNotSatisfiedByAssertedClaimedId" xml:space="preserve"> <value>Sorry. This site only accepts OpenIDs that are HTTPS-secured, but {0} is not a secure Identifier.</value> </data> + <data name="BadExtension" xml:space="preserve"> + <value>The {0} extension failed to deserialize and will be skipped. {1}</value> + </data> <data name="PositiveAssertionFromNonWhitelistedProvider" xml:space="preserve"> <value>An positive OpenID assertion was received from OP endpoint {0} that is not on this relying party's whitelist.</value> </data> -</root>
\ No newline at end of file +</root> diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs index 6f0e7bf..1fed8a6 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs @@ -192,7 +192,7 @@ namespace DotNetOpenAuth.OpenId.Provider { // If the incoming request does not resemble an OpenID message at all, // it's probably a user who just navigated to this URL, and we should // just return null so the host can display a message to the user. - if (httpRequestInfo.HttpMethod == "GET" && !httpRequestInfo.Url.QueryStringContainPrefixedParameters(Protocol.Default.openid.Prefix)) { + if (httpRequestInfo.HttpMethod == "GET" && !httpRequestInfo.UrlBeforeRewriting.QueryStringContainPrefixedParameters(Protocol.Default.openid.Prefix)) { return null; } diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs index 635a3c0..43e666d 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs @@ -114,7 +114,12 @@ namespace DotNetOpenAuth.OpenId.Provider { protected override void OnLoad(EventArgs e) { base.OnLoad(e); - if (this.Enabled) { + // There is the unusual scenario that this control is hosted by + // an ASP.NET web page that has other UI on it to that the user + // might see, including controls that cause a postback to occur. + // We definitely want to ignore postbacks, since any openid messages + // they contain will be old. + if (this.Enabled && !this.Page.IsPostBack) { // Use the explicitly given state store on this control if there is one. // Then try the configuration file specified one. Finally, use the default // in-memory one that's built into OpenIdProvider. diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs index f70bbaa..5ab7ec4 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs @@ -109,6 +109,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public T GetExtension<T>() where T : IOpenIdMessageExtension { throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); } @@ -120,11 +133,73 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public IOpenIdMessageExtension GetExtension(Type extensionType) { throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); } /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); + } + + /// <summary> /// Gets all the callback arguments that were previously added using /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part /// of the return_to URL. diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs index 391aa6e..0dc21bb 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs @@ -143,6 +143,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public T GetExtension<T>() where T : IOpenIdMessageExtension { return default(T); } @@ -154,10 +167,72 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public IOpenIdMessageExtension GetExtension(Type extensionType) { return null; } + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + return default(T); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + return null; + } + #endregion } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs index 7df17b8..afca13d 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs @@ -122,6 +122,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all is required. T is used for return type.")] T GetExtension<T>() where T : IOpenIdMessageExtension; @@ -132,6 +145,66 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> IOpenIdMessageExtension GetExtension(Type extensionType); + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all is required. T is used for return type.")] + T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension; + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + IOpenIdMessageExtension GetUntrustedExtension(Type extensionType); } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs index 0a335c8..cd68a81 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs @@ -168,6 +168,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public T GetExtension<T>() where T : IOpenIdMessageExtension { return default(T); } @@ -179,10 +192,72 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public IOpenIdMessageExtension GetExtension(Type extensionType) { return null; } + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + return default(T); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + return null; + } + #endregion } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs index 1864695..f661789 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs @@ -362,7 +362,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { Uri authUri = new Uri(formAuthData); HttpRequestInfo clientResponseInfo = new HttpRequestInfo { - Url = authUri, + UrlBeforeRewriting = authUri, }; this.authenticationResponse = this.RelyingParty.GetResponse(clientResponseInfo); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs index a065bcd..a7cf801 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs @@ -209,8 +209,21 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public T GetExtension<T>() where T : IOpenIdMessageExtension { - return this.response.Extensions.OfType<T>().FirstOrDefault(); + return this.response.SignedExtensions.OfType<T>().FirstOrDefault(); } /// <summary> @@ -220,8 +233,71 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public IOpenIdMessageExtension GetExtension(Type extensionType) { ErrorUtilities.VerifyArgumentNotNull(extensionType, "extensionType"); + return this.response.SignedExtensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + return this.response.Extensions.OfType<T>().FirstOrDefault(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + ErrorUtilities.VerifyArgumentNotNull(extensionType, "extensionType"); return this.response.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs index 64c6099..f7ac3c2 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs @@ -86,20 +86,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { public TimeSpan PrivateSecretMaximumAge { get; set; } /// <summary> - /// Gets or sets a value indicating whether unsigned extension responses will be deserialized. - /// </summary> - /// <value> - /// <c>false</c> to ignore unsigned extension responses; <c>true</c> to accept them. - /// Default is <c>false</c>. - /// </value> - /// <remarks> - /// This is an internal-only property because not requiring signed extensions is - /// potentially dangerous. It is included here as an internal option primarily - /// to enable testing. - /// </remarks> - internal bool AllowUnsignedIncomingExtensions { get; set; } - - /// <summary> /// Fires the <see cref="RequireSslChanged"/> event. /// </summary> private void OnRequireSslChanged() { diff --git a/src/DotNetOpenAuth/Yadis/Yadis.cs b/src/DotNetOpenAuth/Yadis/Yadis.cs index 151aa63..14aea62 100644 --- a/src/DotNetOpenAuth/Yadis/Yadis.cs +++ b/src/DotNetOpenAuth/Yadis/Yadis.cs @@ -28,7 +28,11 @@ namespace DotNetOpenAuth.Yadis { /// <summary> /// Gets or sets the cache that can be used for HTTP requests made during identifier discovery. /// </summary> +#if DEBUG + internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.BypassCache); +#else internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(DotNetOpenAuthSection.Configuration.OpenId.CacheDiscovery ? HttpRequestCacheLevel.CacheIfAvailable : HttpRequestCacheLevel.BypassCache); +#endif /// <summary> /// The maximum number of bytes to read from an HTTP response diff --git a/src/version.txt b/src/version.txt index b0f2dcb..ef538c2 100644 --- a/src/version.txt +++ b/src/version.txt @@ -1 +1 @@ -3.0.4 +3.1.2 |