-
Notifications
You must be signed in to change notification settings - Fork 163
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Client Credentials Grant auth support (#799)
- Loading branch information
Showing
8 changed files
with
477 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Box.V2.Auth; | ||
using Box.V2.CCGAuth; | ||
using Box.V2.Config; | ||
using Box.V2.Request; | ||
using Box.V2.Services; | ||
using Box.V2.Test.Extensions; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Moq; | ||
|
||
namespace Box.V2.Test | ||
{ | ||
[TestClass] | ||
public class BoxCCGAuthTest : BoxResourceManagerTest | ||
{ | ||
private readonly Mock<IRequestHandler> _handler; | ||
private readonly IBoxService _service; | ||
private readonly Mock<IBoxConfig> _boxConfig; | ||
private readonly BoxCCGAuth _ccgAuth; | ||
|
||
public BoxCCGAuthTest() | ||
{ | ||
// Initial Setup | ||
_handler = new Mock<IRequestHandler>(); | ||
_service = new BoxService(_handler.Object); | ||
_boxConfig = new Mock<IBoxConfig>(); | ||
_boxConfig.SetupGet(x => x.EnterpriseId).Returns("12345"); | ||
_boxConfig.SetupGet(x => x.ClientId).Returns("123"); | ||
_boxConfig.SetupGet(x => x.ClientSecret).Returns("SECRET"); | ||
_boxConfig.SetupGet(x => x.BoxApiHostUri).Returns(new Uri(Constants.BoxApiHostUriString)); | ||
_boxConfig.SetupGet(x => x.BoxAuthTokenApiUri).Returns(new Uri(Constants.BoxAuthTokenApiUriString)); | ||
_ccgAuth = new BoxCCGAuth(_boxConfig.Object, _service); | ||
} | ||
|
||
[TestMethod] | ||
[TestCategory("CI-UNIT-TEST")] | ||
public async Task GetAdminToken_ValidSession() | ||
{ | ||
// Arrange | ||
IBoxRequest boxRequest = null; | ||
_handler.Setup(h => h.ExecuteAsync<OAuthSession>(It.IsAny<BoxRequest>())) | ||
.Returns(Task<IBoxResponse<OAuthSession>>.Factory.StartNew(() => new BoxResponse<OAuthSession>() | ||
{ | ||
Status = ResponseStatus.Success, | ||
ContentString = "{\"access_token\":\"T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl\",\"expires_in\":3600,\"restricted_to\":[],\"token_type\":\"bearer\"}" | ||
})) | ||
.Callback<IBoxRequest>(r => boxRequest = r); | ||
|
||
// Act | ||
var accessToken = await _ccgAuth.AdminTokenAsync(); | ||
|
||
// Assert | ||
Assert.AreEqual("https://api.box.com/oauth2/token", boxRequest.AbsoluteUri.AbsoluteUri); | ||
Assert.AreEqual(RequestMethod.Post, boxRequest.Method); | ||
Assert.IsTrue(boxRequest.PayloadParameters.ContainsKeyValue("grant_type", "client_credentials")); | ||
Assert.IsTrue(boxRequest.PayloadParameters.ContainsKeyValue("client_id", _boxConfig.Object.ClientId)); | ||
Assert.IsTrue(boxRequest.PayloadParameters.ContainsKeyValue("client_secret", _boxConfig.Object.ClientSecret)); | ||
Assert.IsTrue(boxRequest.PayloadParameters.ContainsKeyValue("box_subject_type", "enterprise")); | ||
Assert.IsTrue(boxRequest.PayloadParameters.ContainsKeyValue("box_subject_id", _boxConfig.Object.EnterpriseId)); | ||
|
||
Assert.AreEqual(accessToken, "T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl"); | ||
} | ||
|
||
[TestMethod] | ||
[TestCategory("CI-UNIT-TEST")] | ||
public async Task GetUserToken_ValidSession() | ||
{ | ||
// Arrange | ||
var userId = "22222"; | ||
IBoxRequest boxRequest = null; | ||
_handler.Setup(h => h.ExecuteAsync<OAuthSession>(It.IsAny<BoxRequest>())) | ||
.Returns(Task<IBoxResponse<OAuthSession>>.Factory.StartNew(() => new BoxResponse<OAuthSession>() | ||
{ | ||
Status = ResponseStatus.Success, | ||
ContentString = "{\"access_token\":\"T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl\",\"expires_in\":3600,\"restricted_to\":[],\"token_type\":\"bearer\"}" | ||
})) | ||
.Callback<IBoxRequest>(r => boxRequest = r); | ||
|
||
// Act | ||
var accessToken = await _ccgAuth.UserTokenAsync(userId); | ||
Assert.AreEqual("https://api.box.com/oauth2/token", boxRequest.AbsoluteUri.AbsoluteUri); | ||
Assert.AreEqual(RequestMethod.Post, boxRequest.Method); | ||
Assert.IsTrue(boxRequest.PayloadParameters.ContainsKeyValue("grant_type", "client_credentials")); | ||
Assert.IsTrue(boxRequest.PayloadParameters.ContainsKeyValue("client_id", _boxConfig.Object.ClientId)); | ||
Assert.IsTrue(boxRequest.PayloadParameters.ContainsKeyValue("client_secret", _boxConfig.Object.ClientSecret)); | ||
Assert.IsTrue(boxRequest.PayloadParameters.ContainsKeyValue("box_subject_type", "user")); | ||
Assert.IsTrue(boxRequest.PayloadParameters.ContainsKeyValue("box_subject_id", userId)); | ||
|
||
// Assert | ||
Assert.AreEqual(accessToken, "T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl"); | ||
} | ||
|
||
[TestMethod] | ||
[TestCategory("CI-UNIT-TEST")] | ||
public void UserClient_ShouldReturnUserClientWithSession() | ||
{ | ||
// Act | ||
var userClient = _ccgAuth.UserClient("T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl", "22222"); | ||
|
||
// Assert | ||
Assert.IsInstanceOfType(userClient, typeof(BoxClient)); | ||
Assert.IsInstanceOfType(userClient.Auth, typeof(CCGAuthRepository)); | ||
Assert.IsNotNull(userClient.Auth.Session); | ||
} | ||
|
||
[TestMethod] | ||
[TestCategory("CI-UNIT-TEST")] | ||
public void AdminClient_ShouldReturnAdminClientWithSession() | ||
{ | ||
// Act | ||
var adminClient = _ccgAuth.AdminClient("T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl", "22222", true); | ||
|
||
// Assert | ||
Assert.IsInstanceOfType(adminClient, typeof(BoxClient)); | ||
Assert.IsInstanceOfType(adminClient.Auth, typeof(CCGAuthRepository)); | ||
Assert.IsNotNull(adminClient.Auth.Session); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Box.V2.Auth; | ||
using Box.V2.CCGAuth; | ||
using Box.V2.Config; | ||
using Box.V2.Request; | ||
using Box.V2.Services; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Moq; | ||
|
||
|
||
namespace Box.V2.Test | ||
{ | ||
[TestClass] | ||
public class CCGAuthRepositoryTest : BoxResourceManagerTest | ||
{ | ||
private readonly CCGAuthRepository _userAuthRepository; | ||
private readonly CCGAuthRepository _adminAuthRepository; | ||
private readonly Mock<IBoxConfig> _boxConfig; | ||
private readonly Mock<IRequestHandler> _handler; | ||
private readonly IBoxService _service; | ||
private readonly BoxCCGAuth _ccgAuth; | ||
|
||
private readonly string _userId = "22222"; | ||
|
||
public CCGAuthRepositoryTest() | ||
{ | ||
// Initial Setup | ||
_handler = new Mock<IRequestHandler>(); | ||
_service = new BoxService(_handler.Object); | ||
_boxConfig = new Mock<IBoxConfig>(); | ||
_boxConfig.SetupGet(x => x.EnterpriseId).Returns("12345"); | ||
_boxConfig.SetupGet(x => x.ClientId).Returns("123"); | ||
_boxConfig.SetupGet(x => x.ClientSecret).Returns("SECRET"); | ||
_boxConfig.SetupGet(x => x.BoxApiHostUri).Returns(new Uri(Constants.BoxApiHostUriString)); | ||
_boxConfig.SetupGet(x => x.BoxAuthTokenApiUri).Returns(new Uri(Constants.BoxAuthTokenApiUriString)); | ||
_ccgAuth = new BoxCCGAuth(_boxConfig.Object, _service); | ||
_userAuthRepository = new CCGAuthRepository(null, _ccgAuth, _userId); | ||
_adminAuthRepository = new CCGAuthRepository(null, _ccgAuth, _userId); | ||
} | ||
|
||
[TestMethod] | ||
[TestCategory("CI-UNIT-TEST")] | ||
public async Task RefreshAccessTokenAsync_ForUser_ReturnsUserSession() | ||
{ | ||
// Arrange | ||
_handler.Setup(h => h.ExecuteAsync<OAuthSession>(It.IsAny<BoxRequest>())) | ||
.Returns(Task<IBoxResponse<OAuthSession>>.Factory.StartNew(() => new BoxResponse<OAuthSession>() | ||
{ | ||
Status = ResponseStatus.Success, | ||
ContentString = "{\"access_token\":\"T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl\",\"expires_in\":3600,\"restricted_to\":[],\"token_type\":\"bearer\"}" | ||
})); | ||
|
||
// Act | ||
var session = await _userAuthRepository.RefreshAccessTokenAsync(null); | ||
|
||
// Assert | ||
Assert.AreEqual(session.AccessToken, "T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl"); | ||
Assert.AreEqual(session.TokenType, "bearer"); | ||
Assert.AreEqual(session.RefreshToken, null); | ||
Assert.AreEqual(session.ExpiresIn, 3600); | ||
} | ||
|
||
[TestMethod] | ||
[TestCategory("CI-UNIT-TEST")] | ||
public async Task RefreshAccessTokenAsync_ForAdmin_ReturnsAdminSession() | ||
{ | ||
// Arrange | ||
_handler.Setup(h => h.ExecuteAsync<OAuthSession>(It.IsAny<BoxRequest>())) | ||
.Returns(Task<IBoxResponse<OAuthSession>>.Factory.StartNew(() => new BoxResponse<OAuthSession>() | ||
{ | ||
Status = ResponseStatus.Success, | ||
ContentString = "{\"access_token\":\"T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl\",\"expires_in\":3600,\"restricted_to\":[],\"token_type\":\"bearer\"}" | ||
})); | ||
|
||
// Act | ||
var session = await _adminAuthRepository.RefreshAccessTokenAsync(null); | ||
|
||
// Assert | ||
Assert.AreEqual(session.AccessToken, "T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl"); | ||
Assert.AreEqual(session.TokenType, "bearer"); | ||
Assert.AreEqual(session.RefreshToken, null); | ||
Assert.AreEqual(session.ExpiresIn, 3600); | ||
} | ||
|
||
[TestMethod] | ||
[TestCategory("CI-UNIT-TEST")] | ||
public void LogoutAsync_ThrowsException() | ||
{ | ||
// Act & Assert | ||
Assert.ThrowsExceptionAsync<NotImplementedException>(() => _adminAuthRepository.LogoutAsync()); | ||
} | ||
|
||
[TestMethod] | ||
[TestCategory("CI-UNIT-TEST")] | ||
public void AuthenticateAsync_ThrowsException() | ||
{ | ||
// Act & Assert | ||
Assert.ThrowsExceptionAsync<NotImplementedException>(() => _adminAuthRepository.AuthenticateAsync(null)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace Box.V2.Test.Extensions | ||
{ | ||
public static class DictionaryExtensions | ||
{ | ||
public static bool ContainsKeyValue<T>(this Dictionary<T, T> dictionary, | ||
T expectedKey, T expectedValue) where T : IEquatable<T> | ||
{ | ||
return dictionary.TryGetValue(expectedKey, out T actualValue) && EqualityComparer<T>.Default.Equals(actualValue, expectedValue); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
using System.Threading.Tasks; | ||
using Box.V2.Auth; | ||
using Box.V2.Config; | ||
using Box.V2.Converter; | ||
using Box.V2.Extensions; | ||
using Box.V2.Request; | ||
using Box.V2.Services; | ||
|
||
namespace Box.V2.CCGAuth | ||
{ | ||
public class BoxCCGAuth | ||
{ | ||
private readonly IBoxService _boxService; | ||
private readonly IBoxConfig _boxConfig; | ||
|
||
/// <summary> | ||
/// Constructor for CCG authentication | ||
/// </summary> | ||
/// <param name="boxConfig">Config contains information about client id, client secret, enterprise id.</param> | ||
/// <param name="boxService">Box service is used to perform GetToken requests</param> | ||
public BoxCCGAuth(IBoxConfig boxConfig, IBoxService boxService) | ||
{ | ||
_boxConfig = boxConfig; | ||
_boxService = boxService; | ||
} | ||
|
||
/// <summary> | ||
/// Constructor for CCG authentication with default boxService | ||
/// </summary> | ||
/// <param name="boxConfig">Config contains information about client id, client secret, enterprise id.</param> | ||
public BoxCCGAuth(IBoxConfig boxConfig) : this(boxConfig, new BoxService(new HttpRequestHandler(boxConfig.WebProxy, boxConfig.Timeout))) | ||
{ | ||
|
||
} | ||
|
||
/// <summary> | ||
/// Create admin BoxClient using an admin access token | ||
/// </summary> | ||
/// <param name="adminToken">Admin access token</param> | ||
/// <param name="asUser">The user ID to set as the 'As-User' header parameter; used to make calls in the context of a user using an admin token</param> | ||
/// <param name="suppressNotifications">Whether or not to suppress both email and webhook notifications. Typically used for administrative API calls. Your application must have “Manage an Enterprise” scope, and the user making the API calls is a co-admin with the correct "Edit settings for your company" permission.</param> | ||
/// <returns>BoxClient that uses CCG authentication</returns> | ||
public IBoxClient AdminClient(string adminToken, string asUser = null, bool? suppressNotifications = null) | ||
{ | ||
var adminSession = Session(adminToken); | ||
var authRepo = new CCGAuthRepository(adminSession, this); | ||
var adminClient = new BoxClient(_boxConfig, authRepo, asUser: asUser, suppressNotifications: suppressNotifications); | ||
|
||
return adminClient; | ||
} | ||
|
||
/// <summary> | ||
/// Create user BoxClient using a user access token | ||
/// </summary> | ||
/// <param name="userToken">User access token</param> | ||
/// <param name="userId">Id of the user</param> | ||
/// <returns>BoxClient that uses CCG authentication</returns> | ||
public IBoxClient UserClient(string userToken, string userId) | ||
{ | ||
var userSession = Session(userToken); | ||
var authRepo = new CCGAuthRepository(userSession, this, userId); | ||
var userClient = new BoxClient(_boxConfig, authRepo); | ||
|
||
return userClient; | ||
} | ||
|
||
/// <summary> | ||
/// Get admin token by posting data to auth url | ||
/// </summary> | ||
/// <returns>Admin token</returns> | ||
public async Task<string> AdminTokenAsync() | ||
{ | ||
return (await CCGAuthPostAsync(Constants.RequestParameters.EnterpriseSubType, _boxConfig.EnterpriseId).ConfigureAwait(false)).AccessToken; | ||
} | ||
|
||
/// <summary> | ||
/// Once you have created an App User or Managed User, you can request a User Access Token via the App Auth feature, which will return the OAuth 2.0 access token for the specified User. | ||
/// </summary> | ||
/// <param name="userId">Id of the user</param> | ||
/// <returns>User token</returns> | ||
public async Task<string> UserTokenAsync(string userId) | ||
{ | ||
return (await CCGAuthPostAsync(Constants.RequestParameters.UserSubType, userId).ConfigureAwait(false)).AccessToken; | ||
} | ||
|
||
private async Task<OAuthSession> CCGAuthPostAsync(string subType, string subId) | ||
{ | ||
BoxRequest boxRequest = new BoxRequest(_boxConfig.BoxApiHostUri, Constants.AuthTokenEndpointString) | ||
.Method(RequestMethod.Post) | ||
.Payload(Constants.RequestParameters.GrantType, Constants.RequestParameters.ClientCredentials) | ||
.Payload(Constants.RequestParameters.ClientId, _boxConfig.ClientId) | ||
.Payload(Constants.RequestParameters.ClientSecret, _boxConfig.ClientSecret) | ||
.Payload(Constants.RequestParameters.SubjectType, subType) | ||
.Payload(Constants.RequestParameters.SubjectId, subId); | ||
|
||
var converter = new BoxJsonConverter(); | ||
IBoxResponse<OAuthSession> boxResponse = await _boxService.ToResponseAsync<OAuthSession>(boxRequest).ConfigureAwait(false); | ||
boxResponse.ParseResults(converter); | ||
|
||
return boxResponse.ResponseObject; | ||
|
||
} | ||
|
||
/// <summary> | ||
/// Create OAuth session from token | ||
/// </summary> | ||
/// <param name="token">Access token created by method UserToken, or AdminToken</param> | ||
/// <returns>OAuth session</returns> | ||
public OAuthSession Session(string token) | ||
{ | ||
return new OAuthSession(token, null, Constants.AccessTokenExpirationTime, Constants.BearerTokenType); | ||
} | ||
} | ||
} |
Oops, something went wrong.