diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2010-06-05 09:10:18 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2010-06-05 09:10:18 -0700 |
commit | 5d22560f67cbed3999cbf732bb76d7e002c7d02e (patch) | |
tree | 2e4309feeb8737e45cbe1ecddb72eb3aa7161c3a /src | |
parent | 6ba96779c8b41114c5a3609c73ce371bd846d6c3 (diff) | |
download | DotNetOpenAuth-5d22560f67cbed3999cbf732bb76d7e002c7d02e.zip DotNetOpenAuth-5d22560f67cbed3999cbf732bb76d7e002c7d02e.tar.gz DotNetOpenAuth-5d22560f67cbed3999cbf732bb76d7e002c7d02e.tar.bz2 |
User Agent flow client now works.
Diffstat (limited to 'src')
16 files changed, 262 insertions, 57 deletions
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 7e2e46a..40c196a 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -280,6 +280,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="InfoCard\WellKnownIssuers.cs" /> <Compile Include="Messaging\CachedDirectWebResponse.cs" /> <Compile Include="Messaging\ChannelContract.cs" /> + <Compile Include="Messaging\IHttpIndirectResponse.cs" /> <Compile Include="Messaging\DirectWebRequestOptions.cs" /> <Compile Include="Messaging\EnumerableCache.cs" /> <Compile Include="Messaging\HostErrorException.cs" /> @@ -322,7 +323,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OAuthWrap\ChannelElements\TimestampEncoder.cs" /> <Compile Include="OAuthWrap\ChannelElements\VerificationCode.cs" /> <Compile Include="OAuthWrap\ChannelElements\WebAppVerificationCodeBindingElement.cs" /> - <Compile Include="OAuthWrap\ChannelElements\AuthServerWebServerFlowBindingElement.cs" /> + <Compile Include="OAuthWrap\ChannelElements\AuthServerAllFlowsBindingElement.cs" /> <Compile Include="OAuthWrap\IAccessTokenAnalyzer.cs" /> <Compile Include="OAuthWrap\IAuthorizationServer.cs" /> <Compile Include="OAuthWrap\IAuthorizationState.cs" /> @@ -331,8 +332,10 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OAuthWrap\Messages\Assertion\AssertionRequest.cs" /> <Compile Include="OAuthWrap\Messages\ClientCredentials\ClientCredentialsRequest.cs" /> <Compile Include="OAuthWrap\Messages\Assertion\AssertionSuccessResponse.cs" /> + <Compile Include="OAuthWrap\Messages\IAccessTokenSuccessResponse.cs" /> <Compile Include="OAuthWrap\Messages\IMessageWithClientState.cs" /> <Compile Include="OAuthWrap\Messages\IOAuthDirectResponseFormat.cs" /> + <Compile Include="OAuthWrap\Messages\IRequestWithRedirectUri.cs" /> <Compile Include="OAuthWrap\Messages\RefreshAccessTokenRequest.cs" /> <Compile Include="OAuthWrap\Messages\Device\RichAppAccessTokenRequest.cs" /> <Compile Include="OAuthWrap\Messages\Device\RichAppRequest.cs" /> @@ -351,6 +354,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OAuthWrap\Messages\WebServer\ResponseFormatEncoder.cs" /> <Compile Include="OAuthWrap\ResourceServer.cs" /> <Compile Include="OAuthWrap\StandardAccessTokenAnalyzer.cs" /> + <Compile Include="OAuthWrap\UserAgentClient.cs" /> <Compile Include="OAuthWrap\WebAppAuthorizationServer.cs" /> <Compile Include="OAuthWrap\WrapUtilities.cs" /> <Compile Include="OAuth\ChannelElements\ICombinedOpenIdProviderTokenManager.cs" /> diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index 8f7aca3..1e632ea 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -665,9 +665,15 @@ namespace DotNetOpenAuth.Messaging { OutgoingWebResponse response = null; bool tooLargeForGet = false; if ((message.HttpMethods & HttpDeliveryMethods.GetRequest) == HttpDeliveryMethods.GetRequest) { + bool payloadInFragment = false; + var httpIndirect = message as IHttpIndirectResponse; + if (httpIndirect != null) { + payloadInFragment = httpIndirect.Include301RedirectPayloadInFragment; + } + // First try creating a 301 redirect, and fallback to a form POST // if the message is too big. - response = this.Create301RedirectResponse(message, fields); + response = this.Create301RedirectResponse(message, fields, payloadInFragment); tooLargeForGet = response.Headers[HttpResponseHeader.Location].Length > IndirectMessageGetToPostThreshold; } @@ -695,7 +701,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="fields">The pre-serialized fields from the message.</param> /// <returns>The encoded HTTP response.</returns> [Pure] - protected virtual OutgoingWebResponse Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) { + protected virtual OutgoingWebResponse Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields, bool payloadInFragment = false) { Contract.Requires<ArgumentNullException>(message != null); Contract.Requires<ArgumentException>(message.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); Contract.Requires<ArgumentNullException>(fields != null); @@ -703,7 +709,12 @@ namespace DotNetOpenAuth.Messaging { WebHeaderCollection headers = new WebHeaderCollection(); UriBuilder builder = new UriBuilder(message.Recipient); - MessagingUtilities.AppendQueryArgs(builder, fields); + if (payloadInFragment) { + builder.AppendFragmentArgs(fields); + } else { + builder.AppendQueryArgs(fields); + } + headers.Add(HttpResponseHeader.Location, builder.Uri.AbsoluteUri); Logger.Http.DebugFormat("Redirecting to {0}", builder.Uri.AbsoluteUri); OutgoingWebResponse response = new OutgoingWebResponse { diff --git a/src/DotNetOpenAuth/Messaging/IHttpIndirectResponse.cs b/src/DotNetOpenAuth/Messaging/IHttpIndirectResponse.cs new file mode 100644 index 0000000..f3dde51 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/IHttpIndirectResponse.cs @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------- +// <copyright file="IHttpDirectResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System.Diagnostics.Contracts; + using System.Net; + + /// <summary> + /// An interface that allows indirect response messages to specify + /// HTTP transport specific properties. + /// </summary> + public interface IHttpIndirectResponse { + bool Include301RedirectPayloadInFragment { get; } + } +} diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index c57c7ca..b6a083f 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -967,6 +967,21 @@ namespace DotNetOpenAuth.Messaging { } } + internal static void AppendFragmentArgs(this UriBuilder builder, IEnumerable<KeyValuePair<string, string>> args) { + Contract.Requires<ArgumentNullException>(builder != null); + + if (args != null && args.Count() > 0) { + StringBuilder sb = new StringBuilder(50 + (args.Count() * 10)); + if (!string.IsNullOrEmpty(builder.Fragment)) { + sb.Append(builder.Fragment); + sb.Append('&'); + } + sb.Append(CreateQueryString(args)); + + builder.Fragment = sb.ToString(); + } + } + /// <summary> /// Adds parameters to a query string, replacing parameters that /// match ones that already exist in the query string. diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthServerWebServerFlowBindingElement.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthServerAllFlowsBindingElement.cs index 5974414..16f1b87 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthServerWebServerFlowBindingElement.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthServerAllFlowsBindingElement.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------- -// <copyright file="WebServerFlowBindingElement.cs" company="Andrew Arnott"> +// <copyright file="AuthServerAllFlowsBindingElement.cs" company="Andrew Arnott"> // Copyright (c) Andrew Arnott. All rights reserved. // </copyright> //----------------------------------------------------------------------- @@ -13,11 +13,11 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { using DotNetOpenAuth.OAuthWrap.Messages; using Messaging; - internal class AuthServerWebServerFlowBindingElement : AuthServerBindingElementBase { + internal class AuthServerAllFlowsBindingElement : AuthServerBindingElementBase { /// <summary> - /// Initializes a new instance of the <see cref="AuthServerWebServerFlowBindingElement"/> class. + /// Initializes a new instance of the <see cref="AuthServerAllFlowsBindingElement"/> class. /// </summary> - internal AuthServerWebServerFlowBindingElement() { + internal AuthServerAllFlowsBindingElement() { } /// <summary> @@ -64,10 +64,13 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. /// </remarks> public override MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - var authorizationRequest = message as WebAppRequest; + var authorizationRequest = message as IRequestWithRedirectUri; if (authorizationRequest != null) { var client = this.AuthorizationServer.GetClientOrThrow(authorizationRequest.ClientIdentifier); ErrorUtilities.VerifyProtocol(client.Callback == null || client.Callback == authorizationRequest.Callback, OAuthWrapStrings.CallbackMismatch, client.Callback, authorizationRequest.Callback); + ErrorUtilities.VerifyProtocol(client.Callback != null || authorizationRequest.Callback != null, OAuthWrapStrings.NoCallback); + + return MessageProtections.None; } return null; diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs index cdd665c..252a44b 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs @@ -113,8 +113,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { } /// <summary> - /// Queues a message for sending in the response stream where the fields - /// are sent in the response stream in querystring style. + /// Queues a message for sending in the response stream. /// </summary> /// <param name="response">The message to send as a response.</param> /// <returns> @@ -151,6 +150,24 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { return webResponse; } + protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) { + if (!string.IsNullOrEmpty(request.Url.Fragment)) { + var fields = HttpUtility.ParseQueryString(request.Url.Fragment.Substring(1)).ToDictionary(); + + MessageReceivingEndpoint recipient; + try { + recipient = request.GetRecipient(); + } catch (ArgumentException ex) { + Logger.Messaging.WarnFormat("Unrecognized HTTP request: " + ex.ToString()); + return null; + } + + return (IDirectedProtocolMessage)this.Receive(fields, recipient); + } + + return base.ReadFromRequestCore(request); + } + /// <summary> /// Initializes the binding elements for the OAuth channel. /// </summary> @@ -162,7 +179,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { var bindingElements = new List<IChannelBindingElement>(); if (authorizationServer != null) { - bindingElements.Add(new AuthServerWebServerFlowBindingElement()); + bindingElements.Add(new AuthServerAllFlowsBindingElement()); bindingElements.Add(new WebAppVerificationCodeBindingElement()); bindingElements.Add(new AccessRequestBindingElement()); } diff --git a/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs b/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs index 841aa34..8ca276a 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ClientBase.cs @@ -61,6 +61,7 @@ namespace DotNetOpenAuth.OAuthWrap { public void AuthorizeRequest(HttpWebRequest request, string accessToken) { Contract.Requires<ArgumentNullException>(request != null); Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(accessToken)); + WrapUtilities.AuthorizeWithOAuthWrap(request, accessToken); } @@ -128,6 +129,28 @@ namespace DotNetOpenAuth.OAuthWrap { authorization.SaveChanges(); } + internal void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, IAccessTokenSuccessResponse accessTokenSuccess) { + Contract.Requires<ArgumentNullException>(authorizationState != null, "authorizationState"); + Contract.Requires<ArgumentNullException>(accessTokenSuccess != null, "accessTokenSuccess"); + + authorizationState.AccessToken = accessTokenSuccess.AccessToken; + authorizationState.AccessTokenSecret = accessTokenSuccess.AccessTokenSecret; + authorizationState.RefreshToken = accessTokenSuccess.RefreshToken; + authorizationState.AccessTokenExpirationUtc = DateTime.UtcNow + accessTokenSuccess.Lifetime; + authorizationState.AccessTokenIssueDateUtc = DateTime.UtcNow; + if (accessTokenSuccess.Scope != null && accessTokenSuccess.Scope != authorizationState.Scope) { + if (authorizationState.Scope != null) { + Logger.Wrap.InfoFormat("Requested scope of \"{0}\" changed to \"{1}\" by authorization server.", + authorizationState.Scope, + accessTokenSuccess.Scope); + } + + authorizationState.Scope = accessTokenSuccess.Scope; + } + + authorizationState.SaveChanges(); + } + private double ProportionalLifeRemaining(IAuthorizationState authorization) { Contract.Requires<ArgumentNullException>(authorization != null, "authorization"); Contract.Requires<ArgumentException>(authorization.AccessTokenIssueDateUtc.HasValue); diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenSuccessResponse.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenSuccessResponse.cs index b453b07..13cf250 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenSuccessResponse.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/AccessTokenSuccessResponse.cs @@ -18,7 +18,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// <remarks> /// This message type is shared by the Web App, Rich App, and Username/Password profiles. /// </remarks> - internal class AccessTokenSuccessResponse : MessageBase, IHttpDirectResponse { + internal class AccessTokenSuccessResponse : MessageBase, IHttpDirectResponse, IAccessTokenSuccessResponse { /// <summary> /// Initializes a new instance of the <see cref="AccessTokenSuccessResponse"/> class. /// </summary> @@ -53,14 +53,14 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// </summary> /// <value>The access token.</value> [MessagePart(Protocol.access_token, IsRequired = true, AllowEmpty = false)] - internal string AccessToken { get; set; } + public string AccessToken { get; internal set; } /// <summary> /// Gets or sets the lifetime of the access token. /// </summary> /// <value>The lifetime.</value> [MessagePart(Protocol.expires_in, IsRequired = false, Encoder = typeof(TimespanSecondsEncoder))] - internal TimeSpan? Lifetime { get; set; } + public TimeSpan? Lifetime { get; internal set; } /// <summary> /// Gets or sets the refresh token. @@ -70,7 +70,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// OPTIONAL. The refresh token used to obtain new access tokens using the same end-user access grant as described in Section 6 (Refreshing an Access Token). /// </remarks> [MessagePart(Protocol.refresh_token, IsRequired = false, AllowEmpty = false)] - internal string RefreshToken { get; set; } + public string RefreshToken { get; internal set; } /// <summary> /// Gets or sets the access token secret. @@ -80,7 +80,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// REQUIRED if requested by the client. The corresponding access token secret as requested by the client. /// </remarks> [MessagePart(Protocol.access_token_secret, IsRequired = false, AllowEmpty = false)] - internal string AccessTokenSecret { get; set; } + public string AccessTokenSecret { get; internal set; } /// <summary> /// Gets or sets the scope of access being requested. diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/IAccessTokenSuccessResponse.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/IAccessTokenSuccessResponse.cs new file mode 100644 index 0000000..3aa3616 --- /dev/null +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/IAccessTokenSuccessResponse.cs @@ -0,0 +1,18 @@ +namespace DotNetOpenAuth.OAuthWrap.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + internal interface IAccessTokenSuccessResponse { + string AccessToken { get; } + + string AccessTokenSecret { get; } + + string RefreshToken { get; } + + TimeSpan? Lifetime { get; } + + string Scope { get; } + } +} diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/IRequestWithRedirectUri.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/IRequestWithRedirectUri.cs new file mode 100644 index 0000000..fca1bbd --- /dev/null +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/IRequestWithRedirectUri.cs @@ -0,0 +1,12 @@ +namespace DotNetOpenAuth.OAuthWrap.Messages { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + internal interface IRequestWithRedirectUri { + string ClientIdentifier { get; } + + Uri Callback { get; } + } +} diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentFailedResponse.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentFailedResponse.cs index 5547946..e08d332 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentFailedResponse.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentFailedResponse.cs @@ -11,7 +11,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { using System.Text; using DotNetOpenAuth.Messaging; - internal class UserAgentFailedResponse : MessageBase { + internal class UserAgentFailedResponse : MessageBase, IHttpIndirectResponse { /// <summary> /// A constant parameter that indicates the user refused to grant the requested authorization. /// </summary> @@ -38,5 +38,9 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// </remarks> [MessagePart(Protocol.state, IsRequired = false, AllowEmpty = true)] public string ClientState { get; set; } + + bool IHttpIndirectResponse.Include301RedirectPayloadInFragment { + get { return true; } + } } } diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentRequest.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentRequest.cs index 4aa1021..b675898 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentRequest.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentRequest.cs @@ -11,7 +11,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { using System.Text; using DotNetOpenAuth.Messaging; - internal class UserAgentRequest : MessageBase { + internal class UserAgentRequest : MessageBase, IRequestWithRedirectUri { /// <summary> /// The type of message. /// </summary> @@ -28,14 +28,19 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { } /// <summary> + /// Initializes a new instance of the <see cref="UserAgentRequest"/> class. + /// </summary> + /// <param name="authorizationServer">The authorization server.</param> + internal UserAgentRequest(AuthorizationServerDescription authorizationServer) + : this(authorizationServer.AuthorizationEndpoint, authorizationServer.Version) { + } + + /// <summary> /// Gets or sets state of the client that should be sent back with the authorization response. /// </summary> /// <value> /// An opaque value that Clients can use to maintain state associated with this request. /// </value> - /// <remarks> - /// REQUIRED. The client identifier as described in Section 3.4 (Client Credentials). - /// </remarks> [MessagePart(Protocol.state, IsRequired = false, AllowEmpty = true)] public string ClientState { get; set; } @@ -43,7 +48,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// Gets or sets the identifier by which this client is known to the Authorization Server. /// </summary> [MessagePart(Protocol.client_id, IsRequired = true, AllowEmpty = false)] - internal string ClientIdentifier { get; set; } + public string ClientIdentifier { get; set; } /// <summary> /// Gets or sets the callback URL. @@ -56,7 +61,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// REQUIRED unless a redirection URI has been established between the client and authorization server via other means. An absolute URI to which the authorization server will redirect the user-agent to when the end-user authorization step is completed. The authorization server MAY require the client to pre-register their redirection URI. The redirection URI MUST NOT include a query component as defined by [RFC3986] (Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax,” January 2005.) section 3 if the state parameter is present. /// </remarks> [MessagePart(Protocol.redirect_uri, IsRequired = false, AllowEmpty = false)] - internal Uri Callback { get; set; } + public Uri Callback { get; set; } /// <summary> /// Gets or sets a value indicating whether the authorization server is diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentSuccessResponse.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentSuccessResponse.cs index 9b55647..0b8f6cd 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentSuccessResponse.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentSuccessResponse.cs @@ -11,7 +11,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { using System.Text; using DotNetOpenAuth.Messaging; - internal class UserAgentSuccessResponse : MessageBase { + internal class UserAgentSuccessResponse : MessageBase, IHttpIndirectResponse, IAccessTokenSuccessResponse { /// <summary> /// Initializes a new instance of the <see cref="UserAgentSuccessResponse"/> class. /// </summary> @@ -22,15 +22,19 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { { } + bool IHttpIndirectResponse.Include301RedirectPayloadInFragment { + get { return true; } + } + [MessagePart(Protocol.access_token, IsRequired = true, AllowEmpty = false)] - internal string AccessToken { get; set; } + public string AccessToken { get; internal set; } /// <summary> /// Gets or sets the lifetime of the access token. /// </summary> /// <value>The lifetime.</value> [MessagePart(Protocol.expires_in, IsRequired = false, Encoder = typeof(TimespanSecondsEncoder))] - internal TimeSpan? Lifetime { get; set; } + public TimeSpan? Lifetime { get; internal set; } /// <summary> /// Gets or sets the refresh token. @@ -40,7 +44,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// OPTIONAL. The refresh token used to obtain new access tokens using the same end-user access grant as described in Section 6 (Refreshing an Access Token). /// </remarks> [MessagePart(Protocol.refresh_token, IsRequired = false, AllowEmpty = false)] - internal string RefreshToken { get; set; } + public string RefreshToken { get; internal set; } /// <summary> /// Gets or sets the access token secret. @@ -50,7 +54,11 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// REQUIRED if requested by the client. The corresponding access token secret as requested by the client. /// </remarks> [MessagePart(Protocol.access_token_secret, IsRequired = false, AllowEmpty = false)] - internal string AccessTokenSecret { get; set; } + public string AccessTokenSecret { get; internal set; } + + string IAccessTokenSuccessResponse.Scope { + get { return null; } + } /// <summary> /// Gets or sets the state. diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppRequest.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppRequest.cs index a5abdb2..14c99d9 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppRequest.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebAppRequest.cs @@ -15,7 +15,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// to issue an access token to the Consumer if permission is granted. /// </summary> [Serializable] - public class WebAppRequest : MessageBase, IMessageWithClientState { + public class WebAppRequest : MessageBase, IMessageWithClientState, IRequestWithRedirectUri { /// <summary> /// The type of message. /// </summary> @@ -82,16 +82,6 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { public string ClientIdentifier { get; set; } /// <summary> - /// Gets or sets a value indicating whether the authorization server is - /// required to redirect the browser back to the client immediately. - /// </summary> - /// <remarks> - /// OPTIONAL. The parameter value must be set to true or false. If set to true, the authorization server MUST NOT prompt the end-user to authenticate or approve access. Instead, the authorization server attempts to establish the end-user's identity via other means (e.g. browser cookies) and checks if the end-user has previously approved an identical access request by the same client and if that access grant is still active. If the authorization server does not support an immediate check or if it is unable to establish the end-user's identity or approval status, it MUST deny the request without prompting the end-user. Defaults to false if omitted. - /// </remarks> - [MessagePart(Protocol.immediate, IsRequired = false, AllowEmpty = false)] - internal bool? Immediate { get; set; } - - /// <summary> /// Gets or sets the callback URL. /// </summary> /// <value> @@ -102,6 +92,16 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// REQUIRED unless a redirection URI has been established between the client and authorization server via other means. An absolute URI to which the authorization server will redirect the user-agent to when the end-user authorization step is completed. The authorization server MAY require the client to pre-register their redirection URI. The redirection URI MUST NOT include a query component as defined by [RFC3986] (Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax,” January 2005.) section 3 if the state parameter is present. /// </remarks> [MessagePart(Protocol.redirect_uri, IsRequired = false, AllowEmpty = false)] - internal Uri Callback { get; set; } + public Uri Callback { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the authorization server is + /// required to redirect the browser back to the client immediately. + /// </summary> + /// <remarks> + /// OPTIONAL. The parameter value must be set to true or false. If set to true, the authorization server MUST NOT prompt the end-user to authenticate or approve access. Instead, the authorization server attempts to establish the end-user's identity via other means (e.g. browser cookies) and checks if the end-user has previously approved an identical access request by the same client and if that access grant is still active. If the authorization server does not support an immediate check or if it is unable to establish the end-user's identity or approval status, it MUST deny the request without prompting the end-user. Defaults to false if omitted. + /// </remarks> + [MessagePart(Protocol.immediate, IsRequired = false, AllowEmpty = false)] + internal bool? Immediate { get; set; } } } diff --git a/src/DotNetOpenAuth/OAuthWrap/UserAgentClient.cs b/src/DotNetOpenAuth/OAuthWrap/UserAgentClient.cs new file mode 100644 index 0000000..9296cad --- /dev/null +++ b/src/DotNetOpenAuth/OAuthWrap/UserAgentClient.cs @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------- +// <copyright file="UserAgentClient.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuthWrap { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuthWrap.Messages; + using DotNetOpenAuth.Messaging; + using System.Diagnostics.Contracts; + + public class UserAgentClient : ClientBase { + /// <summary> + /// Initializes a new instance of the <see cref="UserAgentClient"/> class. + /// </summary> + public UserAgentClient(AuthorizationServerDescription authorizationServer) + : base(authorizationServer) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="UserAgentClient"/> class. + /// </summary> + /// <param name="authorizationEndpoint">The authorization endpoint.</param> + public UserAgentClient(Uri authorizationEndpoint) + : base(new AuthorizationServerDescription { AuthorizationEndpoint = authorizationEndpoint }) { + Contract.Requires<ArgumentNullException>(authorizationEndpoint != null, "authorizationEndpoint"); + } + + public Uri RequestUserAuthorization(IAuthorizationState authorization = null, bool immediate = false) { + Contract.Requires<InvalidOperationException>(!string.IsNullOrEmpty(this.ClientIdentifier)); + + if (authorization == null) { + authorization = new AuthorizationState(); + } + + if (authorization.Callback == null) { + authorization.Callback = new Uri("http://localhost/"); + } + + var request = new UserAgentRequest(this.AuthorizationServer) { + ClientIdentifier = this.ClientIdentifier, + Scope = authorization.Scope, + SecretType = authorization.AccessTokenSecretType, + Callback = authorization.Callback, + Immediate = immediate, + }; + + return this.Channel.PrepareResponse(request).GetDirectUriRequest(this.Channel); + } + + public IAuthorizationState ProcessUserAuthorization(Uri actualRedirectUrl, IAuthorizationState authorization = null) { + Contract.Requires<ArgumentNullException>(actualRedirectUrl != null, "actualRedirectUrl"); + + if (authorization == null) { + authorization = new AuthorizationState(); + } + + var carrier = new HttpRequestInfo("GET", actualRedirectUrl, actualRedirectUrl.PathAndQuery, new System.Net.WebHeaderCollection(), null); + IDirectedProtocolMessage response = this.Channel.ReadFromRequest(carrier); + if (response == null) { + return null; + } + + UserAgentSuccessResponse success; + UserAgentFailedResponse failure; + if ((success = response as UserAgentSuccessResponse) != null) { + this.UpdateAuthorizationWithResponse(authorization, success); + } else if ((failure = response as UserAgentFailedResponse) != null) { + authorization.Delete(); + return null; + } else { + ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessageReceivedOfMany); + } + + return authorization; + } + } +} diff --git a/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs b/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs index f3cb5bd..865bf3c 100644 --- a/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs +++ b/src/DotNetOpenAuth/OAuthWrap/WebAppClient.cs @@ -84,22 +84,7 @@ namespace DotNetOpenAuth.OAuthWrap { var accessTokenSuccess = accessTokenResponse as AccessTokenSuccessResponse; var failedAccessTokenResponse = accessTokenResponse as AccessTokenFailedResponse; if (accessTokenSuccess != null) { - authorizationState.AccessToken = accessTokenSuccess.AccessToken; - authorizationState.AccessTokenSecret = accessTokenSuccess.AccessTokenSecret; - authorizationState.RefreshToken = accessTokenSuccess.RefreshToken; - authorizationState.AccessTokenExpirationUtc = DateTime.UtcNow + accessTokenSuccess.Lifetime; - authorizationState.AccessTokenIssueDateUtc = DateTime.UtcNow; - if (accessTokenSuccess.Scope != null && accessTokenSuccess.Scope != authorizationState.Scope) { - if (authorizationState.Scope != null) { - Logger.Wrap.InfoFormat("Requested scope of \"{0}\" changed to \"{1}\" by authorization server.", - authorizationState.Scope, - accessTokenSuccess.Scope); - } - - authorizationState.Scope = accessTokenSuccess.Scope; - } - - authorizationState.SaveChanges(); + this.UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); } else { authorizationState.Delete(); string error = failedAccessTokenResponse != null ? failedAccessTokenResponse.Error : "(unknown)"; |