Skip to content

Commit

Permalink
Merge branch 'release/0.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Jericho committed May 26, 2020
2 parents 9c3eeeb + 80591fa commit 28bef8e
Show file tree
Hide file tree
Showing 17 changed files with 520 additions and 86 deletions.
86 changes: 85 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,85 @@
# ZoomNet
# ZoomNet

[![License](https://img.shields.io/badge/license-MIT-blue.svg)](http://jericho.mit-license.org/)
[![Sourcelink](https://img.shields.io/badge/sourcelink-enabled-brightgreen.svg)](https://github.com/dotnet/sourcelink)

[![Build status](https://ci.appveyor.com/api/projects/status/dak6s7m2b002nuf4/branch/develop?svg=true)](https://ci.appveyor.com/project/Jericho/zoomnet)
[![Tests](https://img.shields.io/appveyor/tests/jericho/zoomnet/master.svg)](https://ci.appveyor.com/project/jericho/zoomnet/build/tests)
[![Coverage Status](https://coveralls.io/repos/github/Jericho/ZoomNet/badge.svg?branch=master)](https://coveralls.io/github/Jericho/ZoomNet?branch=master)
[![CodeFactor](https://www.codefactor.io/repository/github/jericho/zoomnet/badge)](https://www.codefactor.io/repository/github/jericho/zoomnet)

| Release Notes| NuGet (stable) | MyGet (prerelease) |
|--------------|----------------|--------------------|
| [![GitHub release](https://img.shields.io/github/release/jericho/zoomnet.svg)](https://github.com/Jericho/ZoomNet/releases) | [![NuGet Version](http://img.shields.io/nuget/v/ZoomNet.svg)](https://www.nuget.org/packages/ZoomNet/) | [![MyGet Pre Release](https://img.shields.io/myget/jericho/vpre/ZoomNet.svg)](http://myget.org/gallery/jericho) |


## About


## Installation

The easiest way to include ZoomNet in your C# project is by adding the nuget package to your project:

```
PM> Install-Package ZoomNet
```

## .NET framework suport

StrongGrid supports the `4.6.1` .NET framework as well as any framework supporting `.NET Standard 2.0` (which includes `.NET 4.7.2`, `.NET 4.8`, `.NET Core 2.x`, `.NET Core 3.x`,` ASP.NET Core 2.x` and `ASP.NET Core 3.x`).


## Usage

### Connection Information
Before you start using the ZoomNet client, you must decide how you are going to connect to the Zoom API. ZoomNet supports two distinct ways of connecting to Zoom: JWT and OAuth.

#### Connection using JWT
This is the simplest way to connect to the Zoom API. Zoom expects you to use a key and a secret to generate a JSON object with a signed payload and to provide this JSON object with every API request. The good news is that ZoomNet takes care of the intricacies of generating this JSON object: you simply provide the key and the secret and ZommNet takes care of the rest. Super easy!

As the Zoom documentation mentions, this is perfect `if you’re looking to build an app that provides server-to-server interaction with Zoom APIs`.

Here is an except from the Zoom documentation that explains [how to get your API key and secret](https://marketplace.zoom.us/docs/guides/auth/jwt#key-secret):

> JWT apps provide an API Key and Secret required to authenticate with JWT. To access the API Key and Secret, Create a JWT App on the Marketplace. After providing basic information about your app, locate your API Key and Secret in the App Credentials page.
When you have the API key and secret, you can instantiate a 'connection info' object like so:
```csharp
var apiKey = "... your API key ...";
var apiSecret = "... your API secret ...";
var connectionInfo = new JwtConnectionInfo(apiKey, apiSecret);
```

#### Connection using OAuth
Using OAuth is much more complicated than using JWT but at the same time, it is more flexible because you can define which permissions your app requires. When a user installs your app, they are presented with the list of permission your app requires and the are given the opportunity to accept.

The Zoom documentation has a document about [how to create an OAuth app](https://marketplace.zoom.us/docs/guides/build/oauth-app) and another document about the [OAuth autorization flow](https://marketplace.zoom.us/docs/guides/auth/oauth) but I personnality was very confused by the later document so here is a brief step-by-step summary:
- you create an OAuth app, define which permissions your app requires and publish the app in the Zoom marketplace.
- user installs your app. During installation, user is presentd with a screen listing the permissons your app requires. User must click `accept`.
- Zoom generates a "authorization code". This code can be used only once to generate the first access token and refresh token. I CAN'T STRESS THIS ENOUGH: the autorization code can be used only one time. This was the confusing part to me: somehow I didn't understand that this code could be used only one time and I was attempting to use it repeatedly. Zoom would accept the code one time and would reject it subsequently, which lead to many hours of frustration while trying to figure out why the code was sometimes rejected.
- The access token is valid for 60 minutes and must therefore be "refreshed" periodically.

ZoomNet takes care of generating the access token and refresh token but it's your responsability to store these generated values.

```csharp
var clientId = "... your client ID ...";
var clientSecret = "... your client secret ...";
var refreshToken = "... the refresh token previously issued by Zoom ...";
var accessToken = "... the access token previously issued by Zoom ...";
var connectionInfo = new OAuthConnectionInfo(clientId, clientSecret, refreshToken, accessToken,
(newRefreshToken, newAccessToken) =>
{
/*
Save the new refresh token and the access token to
a safe place so you can provide it the next time
you need to instantiate an OAuthConnectionInfo
*/
});
```

### Client

You declare your client variable like so:
```csharp
var zoomClient = new ZoomClient(connectionInfo);
```
29 changes: 22 additions & 7 deletions Source/ZoomNet.IntegrationTests/TestsRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public async Task<int> RunAsync()
// -----------------------------------------------------------------------------
// Do you want to proxy requests through Fiddler? Can be useful for debugging.
var useFiddler = true;
var fiddlerPort = 8866; // By default Fiddler4 uses port 8888 and Fiddler Everywhere uses port 8866

// Do you want to use JWT or OAuth?
var connectionMethod = ConnectionMethods.OAuth;
Expand All @@ -50,20 +51,34 @@ public async Task<int> RunAsync()
IConnectionInfo connectionInfo;
if (connectionMethod == ConnectionMethods.Jwt)
{
var apiKey = Environment.GetEnvironmentVariable("ZOOM_JWT_APIKEY");
var apiSecret = Environment.GetEnvironmentVariable("ZOOM_JWT_APISECRET");
var apiKey = Environment.GetEnvironmentVariable("ZOOM_JWT_APIKEY", EnvironmentVariableTarget.User);
var apiSecret = Environment.GetEnvironmentVariable("ZOOM_JWT_APISECRET", EnvironmentVariableTarget.User);
connectionInfo = new JwtConnectionInfo(apiKey, apiSecret);
}
else
{
var clientId = Environment.GetEnvironmentVariable("ZOOM_OAUTH_CLIENTID");
var clientSecret = Environment.GetEnvironmentVariable("ZOOM_OAUTH_CLIENTSECRET");
var authorizationCode = Environment.GetEnvironmentVariable("ZOOM_OAUTH_AUTHORIZATIONCODE");
connectionInfo = new OAuthConnectionInfo(clientId, clientSecret, authorizationCode);
var clientId = Environment.GetEnvironmentVariable("ZOOM_OAUTH_CLIENTID", EnvironmentVariableTarget.User);
var clientSecret = Environment.GetEnvironmentVariable("ZOOM_OAUTH_CLIENTSECRET", EnvironmentVariableTarget.User);
var refreshToken = Environment.GetEnvironmentVariable("ZOOM_OAUTH_REFRESHTOKEN", EnvironmentVariableTarget.User);
var accessToken = Environment.GetEnvironmentVariable("ZOOM_OAUTH_ACCESSTOKEN", EnvironmentVariableTarget.User);
connectionInfo = new OAuthConnectionInfo(clientId, clientSecret, refreshToken, accessToken,
(newRefreshToken, newAccessToken) =>
{
Environment.SetEnvironmentVariable("ZOOM_OAUTH_REFRESHTOKEN", newRefreshToken, EnvironmentVariableTarget.User);
Environment.SetEnvironmentVariable("ZOOM_OAUTH_ACCESSTOKEN", newAccessToken, EnvironmentVariableTarget.User);
});

//var authorizationCode = "<-- the code generated by Zoom when the app is authorized by the user -->";
//connectionInfo = new OAuthConnectionInfo(clientId, clientSecret, authorizationCode,
// (newRefreshToken, newAccessToken) =>
// {
// Environment.SetEnvironmentVariable("ZOOM_OAUTH_REFRESHTOKEN", newRefreshToken, EnvironmentVariableTarget.User);
// Environment.SetEnvironmentVariable("ZOOM_OAUTH_ACCESSTOKEN", newAccessToken, EnvironmentVariableTarget.User);
// });
}

var userId = Environment.GetEnvironmentVariable("ZOOM_USERID");
var proxy = useFiddler ? new WebProxy("http://localhost:8888") : null;
var proxy = useFiddler ? new WebProxy($"http://localhost:{fiddlerPort}") : null;
var client = new ZoomClient(connectionInfo, proxy, null, _loggerFactory.CreateLogger<ZoomClient>());

// Configure Console
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Logzio.DotNet.NLog" Version="1.0.9" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.6.2" />
<PackageReference Include="Logzio.DotNet.NLog" Version="1.0.10" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.4" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.6.4" />
</ItemGroup>

<ItemGroup>
Expand Down
20 changes: 20 additions & 0 deletions Source/ZoomNet.UnitTests/MockSystemClock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Moq;
using System;
using ZoomNet.Utilities;

namespace ZoomNet.UnitTests
{
internal class MockSystemClock : Mock<ISystemClock>
{
public MockSystemClock(DateTime currentDateTime) :
this(currentDateTime.Year, currentDateTime.Month, currentDateTime.Day, currentDateTime.Hour, currentDateTime.Minute, currentDateTime.Second, currentDateTime.Millisecond)
{ }

public MockSystemClock(int year, int month, int day, int hour, int minute, int second, int millisecond) :
base(MockBehavior.Strict)
{
SetupGet(m => m.Now).Returns(new DateTime(year, month, day, hour, minute, second, millisecond, DateTimeKind.Utc));
SetupGet(m => m.UtcNow).Returns(new DateTime(year, month, day, hour, minute, second, millisecond, DateTimeKind.Utc));
}
}
}
32 changes: 32 additions & 0 deletions Source/ZoomNet.UnitTests/Utils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.Extensions.Logging;
using Pathoschild.Http.Client;
using Pathoschild.Http.Client.Extensibility;
using RichardSzalay.MockHttp;
using System;
using System.Linq;
using ZoomNet.Utilities;

namespace ZoomNet.UnitTests
{
public static class Utils
{
private const string ZOOM_V2_BASE_URI = "https://api.zoom.us/v2";

public static Pathoschild.Http.Client.IClient GetFluentClient(MockHttpMessageHandler httpMessageHandler)
{
var httpClient = httpMessageHandler.ToHttpClient();
var client = new FluentClient(new Uri(ZOOM_V2_BASE_URI), httpClient);
client.SetRequestCoordinator(new ZoomRetryCoordinator(new Http429RetryStrategy(), null));
client.Filters.Remove<DefaultErrorFilter>();
client.Filters.Add(new DiagnosticHandler(LogLevel.Debug, LogLevel.Error));
client.Filters.Add(new ZoomErrorHandler());

return client;
}

public static string GetZoomApiUri(params object[] resources)
{
return resources.Aggregate(ZOOM_V2_BASE_URI, (current, path) => $"{current.TrimEnd('/')}/{path.ToString().TrimStart('/')}");
}
}
}
45 changes: 45 additions & 0 deletions Source/ZoomNet.UnitTests/ZoomClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Shouldly;
using System;
using System.Net;
using Xunit;

namespace ZoomNet.UnitTests
{
public class ZoomClientTests
{
private const string API_KEY = "my_api_key";
private const string API_SECRET = "my_api_secret";

[Fact]
public void Version_is_not_empty()
{
// Arrange

// Act
var result = ZoomClient.Version;

// Assert
result.ShouldNotBeNullOrEmpty();
}

[Fact]
public void Dispose()
{
// Arrange
var connectionInfo = new JwtConnectionInfo(API_KEY, API_SECRET);
var client = new ZoomClient(connectionInfo, (IWebProxy)null);

// Act
client.Dispose();

// Assert
// Nothing to assert. We just want to confirm that 'Dispose' did not throw any exception
}

[Fact]
public void Throws_if_apikey_is_null()
{
Should.Throw<ArgumentNullException>(() => new ZoomClient(new JwtConnectionInfo(null, API_SECRET)));
}
}
}
10 changes: 5 additions & 5 deletions Source/ZoomNet.UnitTests/ZoomNet.UnitTests.csproj
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net461;net472;netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>net461;net472;netcoreapp3.1</TargetFrameworks>
<AssemblyName>ZoomNet.UnitTests</AssemblyName>
<RootNamespace>ZoomNet.UnitTests</RootNamespace>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.1" />
<PackageReference Include="Moq" Version="4.11.0" />
<PackageReference Include="RichardSzalay.MockHttp" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="RichardSzalay.MockHttp" Version="6.0.0" />
<PackageReference Include="Shouldly" Version="3.0.2" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
Expand Down
5 changes: 5 additions & 0 deletions Source/ZoomNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{85E5BB2F
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZoomNet.UnitTests", "ZoomNet.UnitTests\ZoomNet.UnitTests.csproj", "{51646A3A-2B1E-43A6-A37D-7E9B3B965CCD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{466D9D6F-B72C-4239-8293-9F00CFCD7F53}"
ProjectSection(SolutionItems) = preProject
..\README.md = ..\README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
8 changes: 7 additions & 1 deletion Source/ZoomNet/Models/OAuthGrantType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ public enum OAuthGrantType
/// Client Credentials.
/// </summary>
[EnumMember(Value = "client_credentials")]
ClientCredentials
ClientCredentials,

/// <summary>
/// Refresh token.
/// </summary>
[EnumMember(Value = "refresh_token")]
RefreshToken
}
}
Loading

0 comments on commit 28bef8e

Please sign in to comment.