Skip to content

Commit

Permalink
Add support for OAuth in Api calls
Browse files Browse the repository at this point in the history
  • Loading branch information
YomesInc authored Dec 25, 2023
1 parent fc556bc commit 0f7395f
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 8 deletions.
36 changes: 36 additions & 0 deletions CloudinaryDotNet.IntegrationTests/AdminApi/OAuthTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using NUnit.Framework;

namespace CloudinaryDotNet.IntegrationTests.AdminApi
{
public class OAuthTest: IntegrationTestBase
{
private const string FAKE_OAUTH_TOKEN = "MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI4";
private static string m_uniqueImagePublicId;

public override void Initialize()
{
base.Initialize();

m_uniqueImagePublicId = $"asset_image_{m_uniqueTestId}";
}

[Test]
public void TestOAuthToken()
{
var result = m_cloudinary.GetResource(m_uniqueImagePublicId);
Assert.IsNotNull(result.Error);
Assert.IsTrue(result.Error.Message.Contains("Invalid token"));
}

protected override Account GetAccountInstance()
{
Account account = new Account(m_cloudName, FAKE_OAUTH_TOKEN);

Assert.IsNotEmpty(account.Cloud, $"Cloud name must be specified in {CONFIG_PLACE}");
Assert.IsNotEmpty(account.OAuthToken);

return account;
}
}
}
4 changes: 2 additions & 2 deletions CloudinaryDotNet.IntegrationTests/IntegrationTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ private void SaveEmbeddedToDisk(Assembly assembly, string sourcePath, string des
stream.CopyTo(fileStream);
}
}
catch (IOException)
catch
{

}
Expand Down Expand Up @@ -349,7 +349,7 @@ private void PopulateMissingRawUploadParams(RawUploadParams uploadParams, bool i
/// A convenient method for initialization of new Account instance with necessary checks
/// </summary>
/// <returns>New Account instance</returns>
private Account GetAccountInstance()
protected virtual Account GetAccountInstance()
{
Account account = new Account(m_cloudName, m_apiKey, m_apiSecret);

Expand Down
177 changes: 177 additions & 0 deletions CloudinaryDotNet.Tests/ApiAuthorizationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using CloudinaryDotNet.Actions;
using NUnit.Framework;

namespace CloudinaryDotNet.Tests
{
public class ApiAuthorizationTest
{
private const string m_oauthToken = "NTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZj17";
private const string m_cloudName = "test123";
private const string m_apiKey = "key";
private const string m_apiSecret = "secret";
private MockedCloudinary m_mockedCloudinary;

[Test]
public async Task TestOAuthTokenAdminApi()
{
InitCloudinaryApi();

await m_mockedCloudinary.PingAsync();

AssertHasBearerAuthorization(m_mockedCloudinary, m_oauthToken);
}

[Test]
public async Task TestKeyAndSecretAdminApi()
{
InitCloudinaryApi(m_apiKey, m_apiSecret);

await m_mockedCloudinary.PingAsync();

AssertHasBasicAuthorization(m_mockedCloudinary, "a2V5OnNlY3JldA==");
}

[Test]
public async Task TestOAuthTokenUploadApi()
{
InitCloudinaryApi();

var uploadParams = new ImageUploadParams()
{
File = GetFileDescription()
};

await m_mockedCloudinary.UploadAsync(uploadParams);

AssertHasBearerAuthorization(m_mockedCloudinary, m_oauthToken);
Assert.IsFalse(m_mockedCloudinary.HttpRequestContent.Contains("signature"));
}

[Test]
public async Task TestKeyAndSecretUploadApi()
{
InitCloudinaryApi(m_apiKey, m_apiSecret);

var uploadParams = new ImageUploadParams()
{
File = GetFileDescription()
};
await m_mockedCloudinary.UploadAsync(uploadParams);

AssertUploadSignature();
}

[TestCaseSource(typeof(UploadApiProvider), nameof(UploadApiProvider.UploadApis))]
public async Task TestUploadAuthorization(Func<MockedCloudinary, Task> func)
{
InitCloudinaryApi(m_apiKey, m_apiSecret);

await func(m_mockedCloudinary);

AssertUploadSignature();
}

private static FileDescription GetFileDescription()
=> new FileDescription("foo", new MemoryStream(new byte[5]));

private void AssertUploadSignature()
{
var httpRequestContent = m_mockedCloudinary.HttpRequestContent;
Assert.IsTrue(httpRequestContent.Contains("signature"));
Assert.IsTrue(httpRequestContent.Contains("api_key"));
}

[Test]
public async Task TestMissingCredentialsUploadApi()
{
InitCloudinaryApi(null, null);

var uploadParams = new ImageUploadParams()
{
File = new FileDescription(Path.GetTempFileName()),
Unsigned = true,
UploadPreset = "api_test_upload_preset"
};

await m_mockedCloudinary.UploadAsync(uploadParams);

Assert.IsTrue(m_mockedCloudinary.HttpRequestContent.Contains("upload_preset"));
}

private void InitCloudinaryApi()
{
m_mockedCloudinary = new MockedCloudinary(account: new Account(m_cloudName, m_oauthToken));
}

private void InitCloudinaryApi(string apiKey, string apiSecret)
{
m_mockedCloudinary = new MockedCloudinary(account: new Account(m_cloudName, apiKey, apiSecret));
}

private void AssertHasAuthorization(MockedCloudinary cloudinary, string scheme, string value) =>
Assert.AreEqual(cloudinary.HttpRequestHeaders.Authorization, new AuthenticationHeaderValue(scheme, value));

private void AssertHasBearerAuthorization(MockedCloudinary cloudinary, string value) =>
AssertHasAuthorization(cloudinary, "Bearer", value);

private void AssertHasBasicAuthorization(MockedCloudinary cloudinary, string value) =>
AssertHasAuthorization(cloudinary, "Basic", value);

private static class UploadApiProvider
{
public static IEnumerable<object> UploadApis()
{
yield return new Func<MockedCloudinary, Task>[]
{ m => m.UploadAsync(new VideoUploadParams { File = GetFileDescription() }) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.UploadAsync(new ImageUploadParams { File = GetFileDescription() }) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.UploadAsync(new RawUploadParams { File = GetFileDescription() }) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.UploadLargeAsync(new RawUploadParams { File = GetFileDescription() }) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.UploadLargeRawAsync(new RawUploadParams { File = GetFileDescription() }) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.TagAsync(new TagParams()) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.ContextAsync(new ContextParams()) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.ExplicitAsync(new ExplicitParams("id")) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.ExplodeAsync(new ExplodeParams("id", new Transformation())) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.CreateZipAsync(new ArchiveParams().PublicIds(new List<string> { "id" })) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.CreateArchiveAsync(new ArchiveParams().PublicIds(new List<string> { "id" })) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.MakeSpriteAsync(new SpriteParams("tag")) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.MultiAsync(new MultiParams("tag")) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.TextAsync(new TextParams("text")) };

yield return new Func<MockedCloudinary, Task>[]
{ m => m.CreateSlideshowAsync(
new CreateSlideshowParams { ManifestTransformation = new Transformation() }) };
}
}
}
}
9 changes: 6 additions & 3 deletions CloudinaryDotNet.Tests/MockedCloudinary.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
Expand All @@ -14,8 +15,10 @@ public class MockedCloudinary : Cloudinary
public Mock<HttpMessageHandler> HandlerMock;
public string HttpRequestContent;
private const string CloudName = "test123";
public HttpRequestHeaders HttpRequestHeaders;

public MockedCloudinary(string responseStr = "{}", HttpResponseHeaders httpResponseHeaders = null) : base("cloudinary://key:secret@test123")
public MockedCloudinary(string responseStr = "{}", HttpResponseHeaders httpResponseHeaders = null, Account account = null)
: base(account ?? new Account(CloudName, "key", "secret"))
{
HandlerMock = new Mock<HttpMessageHandler>();

Expand Down Expand Up @@ -45,6 +48,7 @@ public MockedCloudinary(string responseStr = "{}", HttpResponseHeaders httpRespo
.ReadAsStringAsync()
.GetAwaiter()
.GetResult();
HttpRequestHeaders = httpRequestMessage.Headers;
})
.ReturnsAsync(httpResponseMessage);
Api.Client = new HttpClient(HandlerMock.Object);
Expand All @@ -70,7 +74,6 @@ public void AssertHttpCall(System.Net.Http.HttpMethod httpMethod, string localPa
);
}


public JToken RequestJson()
{
return JToken.Parse(HttpRequestContent);
Expand Down
17 changes: 17 additions & 0 deletions CloudinaryDotNet/Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ public Account(string cloud, string apiKey, string apiSecret)
ApiSecret = apiSecret;
}

/// <summary>
/// Initializes a new instance of the <see cref="Account"/> class.
/// Parameterized constructor.
/// </summary>
/// <param name="cloud">Cloud name.</param>
/// <param name="oauthToken">OAuth token.</param>
public Account(string cloud, string oauthToken)
: this(cloud)
{
OAuthToken = oauthToken;
}

/// <summary>
/// Initializes a new instance of the <see cref="Account"/> class.
/// Parameterized constructor.
Expand All @@ -54,5 +66,10 @@ public Account(string cloud)
/// Gets or sets API secret.
/// </summary>
public string ApiSecret { get; set; }

/// <summary>
/// Gets or sets oauth token.
/// </summary>
public string OAuthToken { get; set; }
}
}
12 changes: 12 additions & 0 deletions CloudinaryDotNet/Actions/PingResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace CloudinaryDotNet.Actions
{
using System.Runtime.Serialization;

/// <summary>
/// Parsed details of a single ping request.
/// </summary>
[DataContract]
public class PingResult : BaseResult
{
}
}
12 changes: 9 additions & 3 deletions CloudinaryDotNet/ApiShared.Internal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ internal void FinalizeUploadParameters(IDictionary<string, object> parameters)
parameters.Add("timestamp", Utils.UnixTimeNowSeconds());
}

if (!parameters.ContainsKey("signature"))
if (!parameters.ContainsKey("signature") && string.IsNullOrEmpty(Account.OAuthToken))
{
parameters.Add("signature", SignParameters(parameters));
}
Expand Down Expand Up @@ -548,6 +548,13 @@ private void SetChunkedEncoding(HttpRequestMessage request)
}
}

private AuthenticationHeaderValue GetAuthorizationHeaderValue()
{
return !string.IsNullOrEmpty(Account.OAuthToken)
? new AuthenticationHeaderValue("Bearer", Account.OAuthToken)
: new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(GetApiCredentials())));
}

private void PrePrepareRequestBody(
HttpRequestMessage request,
HttpMethod method,
Expand All @@ -562,8 +569,7 @@ private void PrePrepareRequestBody(
: string.Format(CultureInfo.InvariantCulture, "{0} {1}", UserPlatform, USER_AGENT);
request.Headers.Add("User-Agent", userPlatform);

byte[] authBytes = Encoding.ASCII.GetBytes(GetApiCredentials());
request.Headers.Add("Authorization", string.Format(CultureInfo.InvariantCulture, "Basic {0}", Convert.ToBase64String(authBytes)));
request.Headers.Authorization = GetAuthorizationHeaderValue();

if (extraHeaders != null)
{
Expand Down
19 changes: 19 additions & 0 deletions CloudinaryDotNet/Cloudinary.AdminApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,25 @@ public Task<UsageResult> GetUsageAsync(CancellationToken? cancellationToken = nu
cancellationToken);
}

/// <summary>
/// Tests the reachability of the Cloudinary API.
/// </summary>
/// <returns>Ping result.</returns>
public PingResult Ping()
{
return PingAsync(null).GetAwaiter().GetResult();
}

/// <summary>
/// Tests the reachability of the Cloudinary API asynchronously.
/// </summary>
/// <param name="cancellationToken">(Optional) Cancellation token.</param>
/// <returns>Ping result.</returns>
public Task<PingResult> PingAsync(CancellationToken? cancellationToken = null)
{
return CallAdminApiAsync<PingResult>(HttpMethod.GET, GetApiUrlV().BuildUrl("ping"), null, cancellationToken);
}

/// <summary>
/// Gets a list of tags asynchronously.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions CloudinaryDotNet/CloudinaryDotNet.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<LangVersion>9.0</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TargetFrameworks>netstandard1.3;netstandard2.0;net452</TargetFrameworks>
<AssemblyName>CloudinaryDotNet</AssemblyName>
Expand Down

0 comments on commit 0f7395f

Please sign in to comment.