diff options
Diffstat (limited to 'src')
17 files changed, 209 insertions, 77 deletions
diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index ffbacda..b229bb8 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -209,6 +209,15 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Cuts off precision beyond a second on a DateTime value. + /// </summary> + /// <param name="value">The value.</param> + /// <returns>A DateTime with a 0 millisecond component.</returns> + public static DateTime CutToSecond(this DateTime value) { + return value - TimeSpan.FromMilliseconds(value.Millisecond); + } + + /// <summary> /// Strips any and all URI query parameters that serve as parts of a message. /// </summary> /// <param name="uri">The URI that may contain query parameters to remove.</param> diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs index b71b66d..eaa444d 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -52,12 +53,18 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. /// </remarks> public override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - var tokenRequest = message as ITokenCarryingRequest; - if (tokenRequest != null) { - ErrorUtilities.VerifyInternal(tokenRequest.CodeOrTokenType == CodeOrTokenType.AuthorizationCode, "Only verification codes are expected here."); - var tokenBag = (AuthorizationCode)tokenRequest.AuthorizationDescription; - var formatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer); - tokenRequest.CodeOrToken = formatter.Serialize(tokenBag); + var response = message as ITokenCarryingRequest; + if (response != null) { + switch (response.CodeOrTokenType) + { + case CodeOrTokenType.AuthorizationCode: + var codeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer); + var code = (AuthorizationCode)response.AuthorizationDescription; + response.CodeOrToken = codeFormatter.Serialize(code); + break; + default: + throw ErrorUtilities.ThrowInternal(string.Format(CultureInfo.CurrentCulture, "Unexpected outgoing code or token type: {0}", response.CodeOrTokenType)); + } return MessageProtections.None; } diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/ITokenCarryingRequest.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/ITokenCarryingRequest.cs index 0f4d84f..4c8d33f 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/ITokenCarryingRequest.cs +++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/ITokenCarryingRequest.cs @@ -5,6 +5,8 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth2.ChannelElements { + using System.Security.Cryptography; + using Messaging; /// <summary> diff --git a/src/DotNetOpenAuth/OAuth2/ClientBase.cs b/src/DotNetOpenAuth/OAuth2/ClientBase.cs index b2b63aa..46b6af4 100644 --- a/src/DotNetOpenAuth/OAuth2/ClientBase.cs +++ b/src/DotNetOpenAuth/OAuth2/ClientBase.cs @@ -184,6 +184,28 @@ namespace DotNetOpenAuth.OAuth2 { authorizationState.SaveChanges(); } + internal void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAuthCodeResponse authorizationSuccess) { + Contract.Requires<ArgumentNullException>(authorizationState != null, "authorizationState"); + Contract.Requires<ArgumentNullException>(authorizationSuccess != null, "authorizationSuccess"); + + var accessTokenRequest = new AccessTokenAuthorizationCodeRequest(this.AuthorizationServer) { + ClientIdentifier = this.ClientIdentifier, + ClientSecret = this.ClientSecret, + Callback = authorizationState.Callback, + AuthorizationCode = authorizationSuccess.AuthorizationCode, + }; + IProtocolMessage accessTokenResponse = this.Channel.Request(accessTokenRequest); + var accessTokenSuccess = accessTokenResponse as AccessTokenSuccessResponse; + var failedAccessTokenResponse = accessTokenResponse as AccessTokenFailedResponse; + if (accessTokenSuccess != null) { + this.UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); + } else { + authorizationState.Delete(); + string error = failedAccessTokenResponse != null ? failedAccessTokenResponse.Error : "(unknown)"; + ErrorUtilities.ThrowProtocol(OAuthWrapStrings.CannotObtainAccessTokenWithReason, error); + } + } + /// <summary> /// Calculates the fraction of life remaining in an access token. /// </summary> diff --git a/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs b/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs index c99fbc3..83b5191 100644 --- a/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs +++ b/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs @@ -20,7 +20,7 @@ namespace DotNetOpenAuth.OAuth2 { [ContractClass(typeof(IAuthorizationServerContract))] public interface IAuthorizationServer { /// <summary> - /// Gets the secret used to symmetrically encrypt and sign verification codes and refresh tokens. + /// Gets the secret used to symmetrically encrypt and sign authorization codes and refresh tokens. /// </summary> /// <remarks> /// This secret should be kept strictly confidential in the authorization server(s) @@ -39,9 +39,9 @@ namespace DotNetOpenAuth.OAuth2 { RSAParameters AccessTokenSigningPrivateKey { get; } /// <summary> - /// Gets the verification code nonce store to use to ensure that verification codes can only be used once. + /// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once. /// </summary> - /// <value>The verification code nonce store.</value> + /// <value>The authorization code nonce store.</value> INonceStore VerificationCodeNonceStore { get; } /// <summary> @@ -89,7 +89,7 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> - /// Gets the secret used to symmetrically encrypt and sign verification codes and refresh tokens. + /// Gets the secret used to symmetrically encrypt and sign authorization codes and refresh tokens. /// </summary> /// <value></value> /// <remarks> @@ -117,9 +117,9 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> - /// Gets the verification code nonce store to use to ensure that verification codes can only be used once. + /// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once. /// </summary> - /// <value>The verification code nonce store.</value> + /// <value>The authorization code nonce store.</value> INonceStore IAuthorizationServer.VerificationCodeNonceStore { get { Contract.Ensures(Contract.Result<INonceStore>() != null); diff --git a/src/DotNetOpenAuth/OAuth2/IConsumerDescription.cs b/src/DotNetOpenAuth/OAuth2/IConsumerDescription.cs index f0fc20a..4e2bc4f 100644 --- a/src/DotNetOpenAuth/OAuth2/IConsumerDescription.cs +++ b/src/DotNetOpenAuth/OAuth2/IConsumerDescription.cs @@ -6,19 +6,18 @@ namespace DotNetOpenAuth.OAuth2 { using System; - using System.Security.Cryptography.X509Certificates; /// <summary> - /// A description of a consumer from a Service Provider's point of view. + /// A description of a client from an Authorization Server's point of view. /// </summary> public interface IConsumerDescription { /// <summary> - /// Gets the consumer secret. + /// Gets the client secret. /// </summary> string Secret { get; } /// <summary> - /// Gets the callback URI that this consumer has pre-registered with the service provider, if any. + /// Gets the callback URI that this client has pre-registered with the service provider, if any. /// </summary> /// <value>A URI that user authorization responses should be directed to; or <c>null</c> if no preregistered callback was arranged.</value> Uri Callback { get; } diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs index 6cfd715..b125c67 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs @@ -13,7 +13,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { using System.Text; using DotNetOpenAuth.Messaging; - internal class EndUserAuthorizationFailedResponse : MessageBase, IMessageWithClientState { + public class EndUserAuthorizationFailedResponse : MessageBase, IMessageWithClientState { /// <summary> /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessResponseBase"/> class. /// </summary> diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs index 2faa33b..d870aba 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs @@ -84,27 +84,5 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <value>The scope of the access request expressed as a list of space-delimited strings. The value of the scope parameter is defined by the authorization server. If the value contains multiple space-delimited strings, their order does not matter, and each string adds an additional access range to the requested scope.</value> [MessagePart(Protocol.scope, IsRequired = false, AllowEmpty = true)] public string Scope { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether the authorization server is - /// allowed to interact with the user before responding to the client's request. - /// </summary> - /// <remarks> - /// This is internal because it doesn't appear in recent drafts of the spec. - /// </remarks> - internal bool IsUserInteractionAllowed { - get { return !this.Immediate.HasValue || !this.Immediate.Value; } - set { this.Immediate = value ? (bool?)null : true; } - } - - /// <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/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs index d66c2d8..a02c050 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs @@ -7,6 +7,8 @@ namespace DotNetOpenAuth.OAuth2.Messages { using System; using System.Diagnostics.Contracts; + using System.Security.Cryptography; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.ChannelElements; @@ -15,7 +17,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// to indicate that user authorization was granted, and to return the user /// to the Client where they started their experience. /// </summary> - internal abstract class EndUserAuthorizationSuccessResponseBase : MessageBase, IMessageWithClientState { + public abstract class EndUserAuthorizationSuccessResponseBase : MessageBase, IMessageWithClientState { /// <summary> /// Initializes a new instance of the <see cref="EndUserAuthorizationSuccessResponseBase"/> class. /// </summary> diff --git a/src/DotNetOpenAuth/OAuth2/Messages/UnauthorizedResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/UnauthorizedResponse.cs index 90ef662..0f12a8c 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/UnauthorizedResponse.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/UnauthorizedResponse.cs @@ -19,6 +19,17 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <summary> /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class. /// </summary> + /// <param name="exception">The exception.</param> + /// <param name="version">The protocol version.</param> + internal UnauthorizedResponse(ProtocolException exception, Version version = null) + : base(version ?? Protocol.Default.Version) { + Contract.Requires<ArgumentNullException>(exception != null, "exception"); + this.ErrorMessage = exception.Message; + } + + /// <summary> + /// Initializes a new instance of the <see cref="UnauthorizedResponse"/> class. + /// </summary> /// <param name="request">The request.</param> internal UnauthorizedResponse(IDirectedProtocolMessage request) : base(request) { diff --git a/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs b/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs index ccc7e8d..609e0a6 100644 --- a/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs +++ b/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs @@ -17,7 +17,63 @@ namespace DotNetOpenAuth.OAuth2 { /// <summary> /// Some common utility methods for OAuth 2.0. /// </summary> - internal static class OAuthUtilities { + public static class OAuthUtilities { + /// <summary> + /// The delimiter between scope elements. + /// </summary> + private static char[] scopeDelimiter = new char[] { ' ' }; + + /// <summary> + /// The characters that may appear in an access token that is included in an HTTP Authorization header. + /// </summary> + /// <remarks> + /// This is defined in OAuth 2.0 DRAFT 10, section 5.1.1. (http://tools.ietf.org/id/draft-ietf-oauth-v2-10.html#authz-header) + /// </remarks> + private static string accessTokenAuthorizationHeaderAllowedCharacters = MessagingUtilities.UppercaseLetters + + MessagingUtilities.LowercaseLetters + + MessagingUtilities.Digits + + @"!#$%&'()*+-./:<=>?@[]^_`{|}~\,;"; + + /// <summary> + /// Determines whether one given scope is a subset of another scope. + /// </summary> + /// <param name="requestedScope">The requested scope, which may be a subset of <paramref name="grantedScope"/>.</param> + /// <param name="grantedScope">The granted scope, the suspected superset.</param> + /// <returns> + /// <c>true</c> if all the elements that appear in <paramref name="requestedScope"/> also appear in <paramref name="grantedScope"/>; + /// <c>false</c> otherwise. + /// </returns> + public static bool IsScopeSubset(string requestedScope, string grantedScope) { + if (string.IsNullOrEmpty(requestedScope)) { + return true; + } + + if (string.IsNullOrEmpty(grantedScope)) { + return false; + } + + var requestedScopes = new HashSet<string>(requestedScope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries)); + var grantedScopes = new HashSet<string>(grantedScope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries)); + return requestedScopes.IsSubsetOf(grantedScopes); + } + + /// <summary> + /// Identifies individual scope elements + /// </summary> + /// <param name="scope">The scope.</param> + /// <param name="scopeComparer">The scope comparer, allowing scopes to be case sensitive or insensitive. + /// Usually <see cref="StringComparer.Ordinal"/> or <see cref="StringComparer.OrdinalIgnoreCase"/>.</param> + /// <returns></returns> + public static HashSet<string> BreakUpScopes(string scope, StringComparer scopeComparer) { + Contract.Requires<ArgumentNullException>(scopeComparer != null, "scopeComparer"); + + if (string.IsNullOrEmpty(scope)) { + return new HashSet<string>(); + } + + return new HashSet<string>(scope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries), scopeComparer); + } + /// <summary> /// Authorizes an HTTP request using an OAuth 2.0 access token in an HTTP Authorization header. /// </summary> @@ -26,10 +82,12 @@ namespace DotNetOpenAuth.OAuth2 { internal static void AuthorizeWithOAuthWrap(this HttpWebRequest request, string accessToken) { Contract.Requires<ArgumentNullException>(request != null); Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(accessToken)); + ErrorUtilities.VerifyProtocol(accessToken.All(ch => accessTokenAuthorizationHeaderAllowedCharacters.IndexOf(ch) >= 0), "The access token contains characters that must not appear in the HTTP Authorization header."); + request.Headers[HttpRequestHeader.Authorization] = string.Format( CultureInfo.InvariantCulture, Protocol.HttpAuthorizationHeaderFormat, - Uri.EscapeDataString(accessToken)); + accessToken); } /// <summary> diff --git a/src/DotNetOpenAuth/OAuth2/Protocol.cs b/src/DotNetOpenAuth/OAuth2/Protocol.cs index c33f79c..a8a67bb 100644 --- a/src/DotNetOpenAuth/OAuth2/Protocol.cs +++ b/src/DotNetOpenAuth/OAuth2/Protocol.cs @@ -24,14 +24,14 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> internal class Protocol { /// <summary> - /// The HTTP authorization scheme "Token"; + /// The HTTP authorization scheme "OAuth"; /// </summary> - internal const string HttpAuthorizationScheme = "Token"; + internal const string HttpAuthorizationScheme = "OAuth"; /// <summary> /// The format of the HTTP Authorization header value that authorizes OAuth 2.0 requests. /// </summary> - internal const string HttpAuthorizationHeaderFormat = "Token token=\"{0}\""; + internal const string HttpAuthorizationHeaderFormat = "OAuth token=\"{0}\""; /// <summary> /// The "type" string. diff --git a/src/DotNetOpenAuth/OAuth2/ResourceServer.cs b/src/DotNetOpenAuth/OAuth2/ResourceServer.cs index 534f741..f013a5e 100644 --- a/src/DotNetOpenAuth/OAuth2/ResourceServer.cs +++ b/src/DotNetOpenAuth/OAuth2/ResourceServer.cs @@ -10,6 +10,8 @@ namespace DotNetOpenAuth.OAuth2 { using System.Diagnostics.Contracts; using System.Linq; using System.Net; + using System.Security.Principal; + using System.ServiceModel.Channels; using System.Text; using System.Text.RegularExpressions; using System.Web; @@ -87,15 +89,55 @@ namespace DotNetOpenAuth.OAuth2 { throw ErrorUtilities.ThrowProtocol("Bad access token"); } else { - throw ErrorUtilities.ThrowProtocol("Missing access token."); + var response = new UnauthorizedResponse(new ProtocolException("Missing access token")); + + username = null; + scope = null; + return this.Channel.PrepareResponse(response); } } catch (ProtocolException ex) { - var response = new UnauthorizedResponse(request, ex); + var response = request != null ? new UnauthorizedResponse(request, ex) : new UnauthorizedResponse(ex); username = null; scope = null; return this.Channel.PrepareResponse(response); } } + + /// <summary> + /// Discovers what access the client should have considering the access token in the current request. + /// </summary> + /// <param name="httpRequestInfo">The HTTP request info.</param> + /// <param name="principal">The principal that contains the user and roles that the access token is authorized for.</param> + /// <returns> + /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. + /// </returns> + public virtual OutgoingWebResponse VerifyAccess(HttpRequestInfo httpRequestInfo, out IPrincipal principal) { + string username, scope; + var result = this.VerifyAccess(httpRequestInfo, out username, out scope); + if (result == null) { + principal = new OAuth.ChannelElements.OAuthPrincipal(username, scope != null ? scope.Split(' ') : new string[0]); + } else { + principal = null; + } + + return result; + } + + /// <summary> + /// Discovers what access the client should have considering the access token in the current request. + /// </summary> + /// <param name="request">HTTP details from an incoming WCF message.</param> + /// <param name="requestUri">The URI of the WCF service endpoint.</param> + /// <param name="principal">The principal that contains the user and roles that the access token is authorized for.</param> + /// <returns> + /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. + /// </returns> + public virtual OutgoingWebResponse VerifyAccess(HttpRequestMessageProperty request, Uri requestUri, out IPrincipal principal) { + Contract.Requires<ArgumentNullException>(request != null, "request"); + Contract.Requires<ArgumentNullException>(requestUri != null, "requestUri"); + + return this.VerifyAccess(new HttpRequestInfo(request, requestUri), out principal); + } } } diff --git a/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs index f292af5..5e0ea94 100644 --- a/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs +++ b/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs @@ -50,7 +50,7 @@ namespace DotNetOpenAuth.OAuth2 { /// This method also responsible to throw a <see cref="ProtocolException"/> or return /// <c>false</c> when the access token is expired, invalid, or from an untrusted authorization server. /// </remarks> - public bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out string scope) { + public virtual bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out string scope) { var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey); var token = accessTokenFormatter.Deserialize(message, accessToken); user = token.User; diff --git a/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs b/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs index 1a30af0..db73cd9 100644 --- a/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs +++ b/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs @@ -36,6 +36,12 @@ namespace DotNetOpenAuth.OAuth2 { Contract.Requires<ArgumentNullException>(authorizationEndpoint != null, "authorizationEndpoint"); } + // TODO: remove this. user agent clients can't keep secrets. + public new string ClientSecret { + get { return base.ClientSecret; } + set { base.ClientSecret = value; } + } + /// <summary> /// Generates a URL that the user's browser can be directed to in order to authorize /// this client to access protected data at some resource server. @@ -65,7 +71,8 @@ namespace DotNetOpenAuth.OAuth2 { ClientIdentifier = this.ClientIdentifier, Scope = authorization.Scope, Callback = authorization.Callback, - ResponseType = EndUserAuthorizationResponseType.AccessToken, + // TODO: bring back ResponseType = AccessToken, since user agents can't keep secrets, thus can't process authorization codes. + //ResponseType = EndUserAuthorizationResponseType.AccessToken, }; return this.Channel.PrepareResponse(request).GetDirectUriRequest(this.Channel); @@ -90,10 +97,13 @@ namespace DotNetOpenAuth.OAuth2 { return null; } - EndUserAuthorizationSuccessAccessTokenResponse success; + EndUserAuthorizationSuccessAccessTokenResponse accessTokenSuccess; + EndUserAuthorizationSuccessAuthCodeResponse authCodeSuccess; EndUserAuthorizationFailedResponse failure; - if ((success = response as EndUserAuthorizationSuccessAccessTokenResponse) != null) { - this.UpdateAuthorizationWithResponse(authorizationState, success); + if ((accessTokenSuccess = response as EndUserAuthorizationSuccessAccessTokenResponse) != null) { + this.UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); + } else if ((authCodeSuccess = response as EndUserAuthorizationSuccessAuthCodeResponse) != null) { + this.UpdateAuthorizationWithResponse(authorizationState, authCodeSuccess); } else if ((failure = response as EndUserAuthorizationFailedResponse) != null) { authorizationState.Delete(); return null; diff --git a/src/DotNetOpenAuth/OAuth2/WebServerAuthorizationServer.cs b/src/DotNetOpenAuth/OAuth2/WebServerAuthorizationServer.cs index 67ea1d6..d8e0190 100644 --- a/src/DotNetOpenAuth/OAuth2/WebServerAuthorizationServer.cs +++ b/src/DotNetOpenAuth/OAuth2/WebServerAuthorizationServer.cs @@ -45,11 +45,16 @@ namespace DotNetOpenAuth.OAuth2 { return message; } - public void ApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string username, Uri callback = null) { + public void ApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string username, string scope = null, Uri callback = null) { Contract.Requires<ArgumentNullException>(authorizationRequest != null, "authorizationRequest"); - var response = this.PrepareApproveAuthorizationRequest(authorizationRequest, callback); - response.AuthorizingUsername = username; + var response = this.PrepareApproveAuthorizationRequest(authorizationRequest, username, callback); + + // Customize the approved scope if the authorization server has decided to do so. + if (scope != null) { + response.Scope = scope; + } + this.Channel.Send(response); } @@ -71,7 +76,8 @@ namespace DotNetOpenAuth.OAuth2 { if (request != null) { // This convenience method only encrypts access tokens assuming that this auth server // doubles as the resource server. - response = this.PrepareAccessTokenResponse(request, this.AuthorizationServer.AccessTokenSigningPrivateKey); + RSAParameters resourceServerPublicKey = this.AuthorizationServer.AccessTokenSigningPrivateKey; + response = this.PrepareAccessTokenResponse(request, resourceServerPublicKey); return true; } @@ -89,7 +95,7 @@ namespace DotNetOpenAuth.OAuth2 { return request; } - internal EndUserAuthorizationFailedResponse PrepareRejectAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, Uri callback = null) { + public EndUserAuthorizationFailedResponse PrepareRejectAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, Uri callback = null) { Contract.Requires<ArgumentNullException>(authorizationRequest != null, "authorizationRequest"); Contract.Ensures(Contract.Result<EndUserAuthorizationFailedResponse>() != null); @@ -101,8 +107,9 @@ namespace DotNetOpenAuth.OAuth2 { return response; } - internal EndUserAuthorizationSuccessResponseBase PrepareApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, Uri callback = null) { + public EndUserAuthorizationSuccessResponseBase PrepareApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string username, Uri callback = null) { Contract.Requires<ArgumentNullException>(authorizationRequest != null, "authorizationRequest"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(username)); Contract.Ensures(Contract.Result<EndUserAuthorizationSuccessResponseBase>() != null); if (callback == null) { @@ -111,8 +118,7 @@ namespace DotNetOpenAuth.OAuth2 { var client = this.AuthorizationServer.GetClientOrThrow(authorizationRequest.ClientIdentifier); EndUserAuthorizationSuccessResponseBase response; - switch (authorizationRequest.ResponseType) - { + switch (authorizationRequest.ResponseType) { case EndUserAuthorizationResponseType.AccessToken: response = new EndUserAuthorizationSuccessAccessTokenResponse(callback, authorizationRequest); break; @@ -123,7 +129,8 @@ namespace DotNetOpenAuth.OAuth2 { default: throw ErrorUtilities.ThrowInternal("Unexpected response type."); } - + + response.AuthorizingUsername = username; return response; } diff --git a/src/DotNetOpenAuth/OAuth2/WebServerClient.cs b/src/DotNetOpenAuth/OAuth2/WebServerClient.cs index d7116df..9b95677 100644 --- a/src/DotNetOpenAuth/OAuth2/WebServerClient.cs +++ b/src/DotNetOpenAuth/OAuth2/WebServerClient.cs @@ -120,22 +120,7 @@ namespace DotNetOpenAuth.OAuth2 { var failure = response as EndUserAuthorizationFailedResponse; ErrorUtilities.VerifyProtocol(success != null || failure != null, MessagingStrings.UnexpectedMessageReceivedOfMany); if (success != null) { - var accessTokenRequest = new AccessTokenAuthorizationCodeRequest(this.AuthorizationServer) { - ClientIdentifier = this.ClientIdentifier, - ClientSecret = this.ClientSecret, - Callback = authorizationState.Callback, - AuthorizationCode = success.AuthorizationCode, - }; - IProtocolMessage accessTokenResponse = this.Channel.Request(accessTokenRequest); - var accessTokenSuccess = accessTokenResponse as AccessTokenSuccessResponse; - var failedAccessTokenResponse = accessTokenResponse as AccessTokenFailedResponse; - if (accessTokenSuccess != null) { - this.UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); - } else { - authorizationState.Delete(); - string error = failedAccessTokenResponse != null ? failedAccessTokenResponse.Error : "(unknown)"; - ErrorUtilities.ThrowProtocol(OAuthWrapStrings.CannotObtainAccessTokenWithReason, error); - } + UpdateAuthorizationWithResponse(authorizationState, success); } else { // failure Logger.OAuth.Info("User refused to grant the requested authorization at the Authorization Server."); authorizationState.Delete(); |