diff options
Diffstat (limited to 'src')
5 files changed, 120 insertions, 20 deletions
diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs index b8cfbe3..ed9f1d4 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 ObtainClientAccessToken(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/OAuth2/ChannelElements/AccessRequestBindingElement.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessRequestBindingElement.cs index b0cef58..67a69fd 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessRequestBindingElement.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/AccessRequestBindingElement.cs @@ -115,6 +115,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { var authCodeCarrier = message as IAuthorizationCodeCarryingRequest; var refreshTokenCarrier = message as IRefreshTokenCarryingRequest; var resourceOwnerPasswordCarrier = message as AccessTokenResourceOwnerPasswordCredentialsRequest; + var clientCredentialOnly = message as AccessTokenClientCredentialsRequest; if (authCodeCarrier != null) { var authorizationCodeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer); var authorizationCode = authorizationCodeFormatter.Deserialize(message, authCodeCarrier.Code); @@ -125,10 +126,13 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { refreshTokenCarrier.AuthorizationDescription = refreshToken; } else if (resourceOwnerPasswordCarrier != null) { try { - if (this.AuthorizationServer.IsResourceOwnerCredentialValid(resourceOwnerPasswordCarrier.UserName, resourceOwnerPasswordCarrier.Password)) { + if (this.AuthorizationServer.IsResourceOwnerCredentialValid(resourceOwnerPasswordCarrier.UserName, + resourceOwnerPasswordCarrier.Password)) { resourceOwnerPasswordCarrier.CredentialsValidated = true; } else { - Logger.OAuth.WarnFormat("Resource owner password credential for user \"{0}\" rejected by authorization server host.", resourceOwnerPasswordCarrier.UserName); + Logger.OAuth.WarnFormat( + "Resource owner password credential for user \"{0}\" rejected by authorization server host.", + resourceOwnerPasswordCarrier.UserName); // TODO: fix this to report the appropriate error code for a bad credential. throw new ProtocolException(); @@ -140,6 +144,9 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { // TODO: fix this to return the appropriate error code for not supporting resource owner password credentials throw new ProtocolException(); } + } else if (clientCredentialOnly != null) { + // this method will throw later if the credentials are false. + clientCredentialOnly.CredentialsValidated = true; } else { throw ErrorUtilities.ThrowInternal("Unexpected message type: " + tokenRequest.GetType()); } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IAuthorizationDescription.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IAuthorizationDescription.cs index 150a6a9..9c4219c 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IAuthorizationDescription.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/ChannelElements/IAuthorizationDescription.cs @@ -68,11 +68,13 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { } /// <summary> - /// Gets the name on the account whose data on the resource server is accessible using this authorization. + /// Gets the name on the account whose data on the resource server is accessible using this authorization, if applicable. /// </summary> + /// <value>A username, or <c>null</c> if the authorization is to access the client's own data (not a distinct resource owner's data).</value> string IAuthorizationDescription.User { get { - Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>())); + // Null and non-empty are allowed, but not empty. + Contract.Ensures(Contract.Result<string>() != String.Empty); throw new NotImplementedException(); } } diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenClientCredentialsRequest.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenClientCredentialsRequest.cs index 266dbce..48419eb 100644 --- a/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenClientCredentialsRequest.cs +++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Messages/AccessTokenClientCredentialsRequest.cs @@ -19,7 +19,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <remarks> /// This is somewhat analogous to 2-legged OAuth. /// </remarks> - internal class AccessTokenClientCredentialsRequest : ScopedAccessTokenRequest { + internal class AccessTokenClientCredentialsRequest : ScopedAccessTokenRequest, IAuthorizationCarryingRequest, IAuthorizationDescription { /// <summary> /// Initializes a new instance of the <see cref="AccessTokenClientCredentialsRequest"/> class. /// </summary> @@ -30,6 +30,43 @@ namespace DotNetOpenAuth.OAuth2.Messages { this.HttpMethods = HttpDeliveryMethods.PostRequest; } + #region IAuthorizationCarryingRequest members + + /// <summary> + /// Gets the authorization that the code or token describes. + /// </summary> + IAuthorizationDescription IAuthorizationCarryingRequest.AuthorizationDescription { + get { return this.CredentialsValidated ? this : null; } + } + + #endregion + + #region IAuthorizationDescription Members + + /// <summary> + /// Gets the date this authorization was established or the token was issued. + /// </summary> + /// <value>A date/time expressed in UTC.</value> + DateTime IAuthorizationDescription.UtcIssued { + get { return DateTime.UtcNow; } + } + + /// <summary> + /// Gets the name on the account whose data on the resource server is accessible using this authorization. + /// </summary> + string IAuthorizationDescription.User { + get { return null; } + } + + /// <summary> + /// Gets the scope of operations the client is allowed to invoke. + /// </summary> + HashSet<string> IAuthorizationDescription.Scope { + get { return this.Scope; } + } + + #endregion + /// <summary> /// Gets the type of the grant. /// </summary> @@ -37,5 +74,10 @@ namespace DotNetOpenAuth.OAuth2.Messages { internal override GrantType GrantType { get { return Messages.GrantType.ClientCredentials; } } + + /// <summary> + /// Gets or sets a value indicating whether the resource owner's credentials have been validated at the authorization server. + /// </summary> + internal bool CredentialsValidated { get; set; } } } diff --git a/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs b/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs index 1615f97..a284840 100644 --- a/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs @@ -10,7 +10,9 @@ namespace DotNetOpenAuth.Test.OAuth2 { using System.Linq; using System.Text; using DotNetOpenAuth.OAuth2; + using DotNetOpenAuth.OAuth2.ChannelElements; using DotNetOpenAuth.OAuth2.Messages; + using Moq; using NUnit.Framework; [TestFixture] @@ -60,5 +62,28 @@ namespace DotNetOpenAuth.Test.OAuth2 { }); coordinator.Run(); } + + [TestCase] + public void ClientCredentialGrant() { + var authServer = CreateAuthorizationServerMock(); + authServer.Setup( + a => a.IsAuthorizationValid(It.Is<IAuthorizationDescription>(d => d.User == null && d.ClientIdentifier == ClientId))) + .Returns(true); + var coordinator = new OAuth2Coordinator<WebServerClient>( + AuthorizationServerDescription, + authServer.Object, + new WebServerClient(AuthorizationServerDescription), + client => { + var authState = client.ObtainClientAccessToken(); + Assert.IsNotNullOrEmpty(authState.AccessToken); + Assert.IsNull(authState.RefreshToken); + }, + server => { + var request = server.ReadAccessTokenRequest(); + var response = server.PrepareAccessTokenResponse(request); + server.Channel.Respond(response); + }); + coordinator.Run(); + } } } |