diff options
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2.Client')
5 files changed, 73 insertions, 32 deletions
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs index b8cfbe3..7697244 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs @@ -167,27 +167,22 @@ namespace DotNetOpenAuth.OAuth2 { Requires.NotNullOrEmpty(userName, "userName"); Requires.NotNull(password, "password"); - var authorizationState = new AuthorizationState(scopes); - var request = new AccessTokenResourceOwnerPasswordCredentialsRequest(this.AuthorizationServer.TokenEndpoint, this.AuthorizationServer.Version) { - ClientIdentifier = this.ClientIdentifier, - ClientSecret = this.ClientSecret, UserName = userName, Password = password, }; - var response = this.Channel.Request(request); - var success = response as AccessTokenSuccessResponse; - var failure = response as AccessTokenFailedResponse; - ErrorUtilities.VerifyProtocol(success != null || failure != null, MessagingStrings.UnexpectedMessageReceivedOfMany); - if (success != null) { - UpdateAuthorizationWithResponse(authorizationState, success); - } else { // failure - Logger.OAuth.Info("Resource Owner credentials rejected by the Authorization Server."); - authorizationState.Delete(); - } + return this.RequestAccessToken(request, scopes); + } - return authorizationState; + /// <summary> + /// Obtains an access token for accessing client-controlled resources on the resource server. + /// </summary> + /// <param name="scopes">The desired scopes.</param> + /// <returns>The result of the authorization request.</returns> + public IAuthorizationState GetClientAccessToken(IEnumerable<string> scopes = null) { + var request = new AccessTokenClientCredentialsRequest(this.AuthorizationServer.TokenEndpoint, this.AuthorizationServer.Version); + return this.RequestAccessToken(request, scopes); } /// <summary> @@ -287,5 +282,34 @@ namespace DotNetOpenAuth.OAuth2 { double proportionLifetimeRemaining = 1 - (elapsedLifetime.TotalSeconds / totalLifetime.TotalSeconds); return proportionLifetimeRemaining; } + + /// <summary> + /// Requests an access token using a partially .initialized request message. + /// </summary> + /// <param name="request">The request message.</param> + /// <param name="scopes">The scopes requested by the client.</param> + /// <returns>The result of the request.</returns> + private IAuthorizationState RequestAccessToken(ScopedAccessTokenRequest request, IEnumerable<string> scopes = null) { + Requires.NotNull(request, "request"); + + var authorizationState = new AuthorizationState(scopes); + + request.ClientIdentifier = this.ClientIdentifier; + request.ClientSecret = this.ClientSecret; + request.Scope.UnionWith(authorizationState.Scope); + + var response = this.Channel.Request(request); + var success = response as AccessTokenSuccessResponse; + var failure = response as AccessTokenFailedResponse; + ErrorUtilities.VerifyProtocol(success != null || failure != null, MessagingStrings.UnexpectedMessageReceivedOfMany); + if (success != null) { + UpdateAuthorizationWithResponse(authorizationState, success); + } else { // failure + Logger.OAuth.Info("Credentials rejected by the Authorization Server."); + authorizationState.Delete(); + } + + return authorizationState; + } } } diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/OAuth2Strings.Designer.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/OAuth2Strings.Designer.cs index d140b7e..74c0685 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/OAuth2Strings.Designer.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/OAuth2Strings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30319.239 +// Runtime Version:4.0.30319.261 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/OAuth2Strings.resx b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/OAuth2Strings.resx index 114b3e8..0a41e42 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/OAuth2Strings.resx +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/OAuth2Strings.resx @@ -125,6 +125,7 @@ </data> <data name="AuthorizationResponseUnexpectedMismatch" xml:space="preserve"> <value>Unexpected OAuth authorization response received with callback and client state that does not match an expected value.</value> + <comment>The error message generated when detecting a mismatch between the state sent to the authorization server originally and what we got back with successful authorization, or that the user sessions were not identical between the two requests, suggesting XSRF or other attack on the user (victim).</comment> </data> <data name="RequiredPropertyNotYetPreset" xml:space="preserve"> <value>The property {0} must be set before this operation is allowed.</value> diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs index 5131b10..c29d167 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/UserAgentClient.cs @@ -10,6 +10,8 @@ namespace DotNetOpenAuth.OAuth2 { using System.Diagnostics.Contracts; using System.Linq; using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth2.Messages; @@ -56,7 +58,7 @@ namespace DotNetOpenAuth.OAuth2 { Callback = returnTo, }; - return this.RequestUserAuthorization(authorization); + return this.RequestUserAuthorization(authorization, state: state); } /// <summary> @@ -93,7 +95,7 @@ namespace DotNetOpenAuth.OAuth2 { authorizationState = new AuthorizationState(); } - var carrier = new HttpRequestInfo("GET", actualRedirectUrl, actualRedirectUrl.PathAndQuery, new System.Net.WebHeaderCollection(), null); + var carrier = new HttpRequestInfo("GET", actualRedirectUrl); IDirectedProtocolMessage response = this.Channel.ReadFromRequest(carrier); if (response == null) { return null; @@ -116,12 +118,11 @@ namespace DotNetOpenAuth.OAuth2 { EndUserAuthorizationSuccessAccessTokenResponse accessTokenSuccess; EndUserAuthorizationSuccessAuthCodeResponse authCodeSuccess; - EndUserAuthorizationFailedResponse failure; if ((accessTokenSuccess = response as EndUserAuthorizationSuccessAccessTokenResponse) != null) { UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); } else if ((authCodeSuccess = response as EndUserAuthorizationSuccessAuthCodeResponse) != null) { this.UpdateAuthorizationWithResponse(authorizationState, authCodeSuccess); - } else if ((failure = response as EndUserAuthorizationFailedResponse) != null) { + } else if (response is EndUserAuthorizationFailedResponse) { authorizationState.Delete(); return null; } diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs index ffcc1ee..671214f 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs @@ -39,43 +39,40 @@ namespace DotNetOpenAuth.OAuth2 { /// Prepares a request for user authorization from an authorization server. /// </summary> /// <param name="scope">The scope of authorized access requested.</param> - /// <param name="state">The state of the client that should be sent back with the authorization response.</param> /// <param name="returnTo">The URL the authorization server should redirect the browser (typically on this site) to when the authorization is completed. If null, the current request's URL will be used.</param> - public void RequestUserAuthorization(IEnumerable<string> scope = null, string state = null, Uri returnTo = null) { + public void RequestUserAuthorization(IEnumerable<string> scope = null, Uri returnTo = null) { var authorizationState = new AuthorizationState(scope) { Callback = returnTo, }; - this.PrepareRequestUserAuthorization(authorizationState, state).Send(); + this.PrepareRequestUserAuthorization(authorizationState).Send(); } /// <summary> /// Prepares a request for user authorization from an authorization server. /// </summary> /// <param name="scopes">The scope of authorized access requested.</param> - /// <param name="state">The state of the client that should be sent back with the authorization response.</param> /// <param name="returnTo">The URL the authorization server should redirect the browser (typically on this site) to when the authorization is completed. If null, the current request's URL will be used.</param> /// <returns>The authorization request.</returns> - public OutgoingWebResponse PrepareRequestUserAuthorization(IEnumerable<string> scopes = null, string state = null, Uri returnTo = null) { + public OutgoingWebResponse PrepareRequestUserAuthorization(IEnumerable<string> scopes = null, Uri returnTo = null) { var authorizationState = new AuthorizationState(scopes) { Callback = returnTo, }; - return this.PrepareRequestUserAuthorization(authorizationState, state); + return this.PrepareRequestUserAuthorization(authorizationState); } /// <summary> /// Prepares a request for user authorization from an authorization server. /// </summary> /// <param name="authorization">The authorization state to associate with this particular request.</param> - /// <param name="state">The state of the client that should be sent back with the authorization response.</param> /// <returns>The authorization request.</returns> - public OutgoingWebResponse PrepareRequestUserAuthorization(IAuthorizationState authorization, string state = null) { + public OutgoingWebResponse PrepareRequestUserAuthorization(IAuthorizationState authorization) { Requires.NotNull(authorization, "authorization"); Requires.ValidState(authorization.Callback != null || (HttpContext.Current != null && HttpContext.Current.Request != null), MessagingStrings.HttpContextRequired); Requires.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), OAuth2Strings.RequiredPropertyNotYetPreset, "ClientIdentifier"); Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); if (authorization.Callback == null) { - authorization.Callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting + authorization.Callback = this.Channel.GetRequestFromContext().GetPublicFacingUrl() .StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(EndUserAuthorizationSuccessResponseBase), Protocol.Default.Version)) .StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(EndUserAuthorizationFailedResponse), Protocol.Default.Version)); authorization.SaveChanges(); @@ -84,10 +81,21 @@ namespace DotNetOpenAuth.OAuth2 { var request = new EndUserAuthorizationRequest(this.AuthorizationServer) { ClientIdentifier = this.ClientIdentifier, Callback = authorization.Callback, - ClientState = state, }; request.Scope.ResetContents(authorization.Scope); + // Mitigate XSRF attacks by including a state value that would be unpredictable between users, but + // verifiable for the same user/session. + // If the host is implementing the authorization tracker though, they're handling this protection themselves. + if (this.AuthorizationTracker == null) { + var context = this.Channel.GetHttpContext(); + if (context.Session != null) { + request.ClientState = context.Session.SessionID; + } else { + Logger.OAuth.WarnFormat("No request context discovered, so no client state parameter could be set to mitigate XSRF attacks."); + } + } + return this.Channel.PrepareResponse(request); } @@ -96,7 +104,7 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="request">The incoming HTTP request that may carry an authorization response.</param> /// <returns>The authorization state that contains the details of the authorization.</returns> - public IAuthorizationState ProcessUserAuthorization(HttpRequestInfo request = null) { + public IAuthorizationState ProcessUserAuthorization(HttpRequestBase request = null) { Requires.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), OAuth2Strings.RequiredPropertyNotYetPreset, "ClientIdentifier"); Requires.ValidState(!string.IsNullOrEmpty(this.ClientSecret), OAuth2Strings.RequiredPropertyNotYetPreset, "ClientSecret"); @@ -106,12 +114,19 @@ namespace DotNetOpenAuth.OAuth2 { IMessageWithClientState response; if (this.Channel.TryReadFromRequest<IMessageWithClientState>(request, out response)) { - Uri callback = MessagingUtilities.StripMessagePartsFromQueryString(request.UrlBeforeRewriting, this.Channel.MessageDescriptions.Get(response)); + Uri callback = MessagingUtilities.StripMessagePartsFromQueryString(request.GetPublicFacingUrl(), this.Channel.MessageDescriptions.Get(response)); IAuthorizationState authorizationState; if (this.AuthorizationTracker != null) { authorizationState = this.AuthorizationTracker.GetAuthorizationState(callback, response.ClientState); ErrorUtilities.VerifyProtocol(authorizationState != null, OAuth2Strings.AuthorizationResponseUnexpectedMismatch); } else { + var context = this.Channel.GetHttpContext(); + if (context.Session != null) { + ErrorUtilities.VerifyProtocol(string.Equals(response.ClientState, context.Session.SessionID, StringComparison.Ordinal), OAuth2Strings.AuthorizationResponseUnexpectedMismatch); + } else { + Logger.OAuth.WarnFormat("No request context discovered, so no client state parameter could be checked to mitigate XSRF attacks."); + } + authorizationState = new AuthorizationState { Callback = callback }; } var success = response as EndUserAuthorizationSuccessAuthCodeResponse; |