Skip to content
This repository has been archived by the owner on Mar 20, 2019. It is now read-only.

Commit

Permalink
Added client credential grant type support and a test to prove it.
Browse files Browse the repository at this point in the history
Fixes #33
  • Loading branch information
AArnott committed Feb 25, 2012
1 parent 98f5559 commit 22bc9e0
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 20 deletions.
54 changes: 39 additions & 15 deletions src/DotNetOpenAuth.OAuth2.Client/OAuth2/ClientBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,27 +167,22 @@ public IAuthorizationState ExchangeUserCredentialForToken(string userName, strin
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>
Expand Down Expand Up @@ -287,5 +282,34 @@ private static double ProportionalLifeRemaining(IAuthorizationState authorizatio
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public override MessageProtections Protection {
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);
Expand All @@ -125,10 +126,13 @@ public override MessageProtections Protection {
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();
Expand All @@ -140,6 +144,9 @@ public override MessageProtections Protection {
// 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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,13 @@ DateTime IAuthorizationDescription.UtcIssued {
}

/// <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();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -30,12 +30,54 @@ internal AccessTokenClientCredentialsRequest(Uri tokenEndpoint, Version version)
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>
/// <value>The type of the grant.</value>
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; }
}
}
25 changes: 25 additions & 0 deletions src/DotNetOpenAuth.Test/OAuth2/WebServerClientAuthorizeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -60,5 +62,28 @@ public void ResourceOwnerPasswordCredentialGrant() {
});
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();
}
}
}

0 comments on commit 22bc9e0

Please sign in to comment.