Skip to content

Commit

Permalink
Add the HasPermissions method to the ZoomClient class
Browse files Browse the repository at this point in the history
Resolves #379
  • Loading branch information
Jericho committed Nov 11, 2024
1 parent 2a35741 commit 255a8aa
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 23 deletions.
25 changes: 14 additions & 11 deletions Source/ZoomNet.IntegrationTests/Tests/Accounts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,23 @@ public async Task RunAsync(User myUser, string[] myPermissions, IZoomClient clie

await log.WriteLineAsync("\n***** ACCOUNTS *****\n").ConfigureAwait(false);

// GET ALL THE ACCOUNTS
var paginatedAccounts = await client.Accounts.GetAllAsync(100, null, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync($"There are {paginatedAccounts.TotalRecords} sub accounts under the main account").ConfigureAwait(false);

// GET SETTINGS
if (paginatedAccounts.Records.Any())
if (client.HasPermission("account:read:list_sub_accounts:master"))
{
var accountId = paginatedAccounts.Records.First().Id;
// GET ALL THE ACCOUNTS
var paginatedAccounts = await client.Accounts.GetAllAsync(100, null, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync($"There are {paginatedAccounts.TotalRecords} sub accounts under the main account").ConfigureAwait(false);

// GET SETTINGS
if (paginatedAccounts.Records.Any())
{
var accountId = paginatedAccounts.Records.First().Id;

var meetingAuthenticationSettings = await client.Accounts.GetMeetingAuthenticationSettingsAsync(accountId, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync("Meeting authentication settings retrieved").ConfigureAwait(false);
var meetingAuthenticationSettings = await client.Accounts.GetMeetingAuthenticationSettingsAsync(accountId, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync("Meeting authentication settings retrieved").ConfigureAwait(false);

var recordingAuthenticationSettings = await client.Accounts.GetRecordingAuthenticationSettingsAsync(accountId, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync("Recording authentication settings retrieved").ConfigureAwait(false);
var recordingAuthenticationSettings = await client.Accounts.GetRecordingAuthenticationSettingsAsync(accountId, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync("Recording authentication settings retrieved").ConfigureAwait(false);
}
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions Source/ZoomNet/Extensions/Public.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,5 +368,22 @@ public static async Task<string> AddUserToGroupAsync(this IGroups groupsResource
// We added a single member to a group therefore the array returned from the Zoom API contains a single element
return result.Single();
}

/// <summary>
/// Determines if the specified scope has been granted.
/// </summary>
/// <param name="client">The ZoomNet client.</param>
/// <param name="scope">The name of the scope.</param>
/// <returns>True if the scope has been granted, False otherwise.</returns>
/// <remarks>
/// The concept of "scopes" only applies to OAuth connections.
/// Therefore an exeption will be thrown if you invoke this method while using
/// a JWT connection (you shouldn't be using JWT in the first place since this
/// type of connection has been deprecated in the Zoom API since September 2023).
/// </remarks>
public static bool HasPermission(this IZoomClient client, string scope)
{
return client.HasPermissions(new[] { scope });
}
}
}
14 changes: 14 additions & 0 deletions Source/ZoomNet/IZoomClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using ZoomNet.Resources;

namespace ZoomNet
Expand Down Expand Up @@ -105,5 +106,18 @@ public interface IZoomClient
/// Gets the resource which allows you to manage webinars.
/// </summary>
IWebinars Webinars { get; }

/// <summary>
/// Determines if the specified scopes have been granted.
/// </summary>
/// <param name="scopes">The name of the scopes.</param>
/// <returns>True if all the scopes have been granted, False otherwise.</returns>
/// <remarks>
/// The concept of "scopes" only applies to OAuth connections.
/// Therefore an exeption will be thrown if you invoke this method while using
/// a JWT connection (you shouldn't be using JWT in the first place since this
/// type of connection has been deprecated in the Zoom API since September 2023).
/// </remarks>
bool HasPermissions(IEnumerable<string> scopes);
}
}
4 changes: 2 additions & 2 deletions Source/ZoomNet/OAuthConnectionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ public class OAuthConnectionInfo : IConnectionInfo
public string AccessToken { get; internal set; }

/// <summary>
/// Gets the token scope.
/// Gets the scopes.
/// </summary>
public IReadOnlyDictionary<string, string[]> TokenScope { get; internal set; }
public IReadOnlyList<string> Scopes { get; internal set; }

/// <summary>
/// Gets the token expiration time.
Expand Down
11 changes: 1 addition & 10 deletions Source/ZoomNet/Utilities/OAuthTokenHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,7 @@ public string RefreshTokenIfNecessary(bool forceRefresh)
_connectionInfo.RefreshToken = jsonResponse.GetPropertyValue("refresh_token", string.Empty);
_connectionInfo.AccessToken = jsonResponse.GetPropertyValue("access_token", string.Empty);
_connectionInfo.TokenExpiration = requestTime.AddSeconds(jsonResponse.GetPropertyValue("expires_in", 60 * 60));
_connectionInfo.TokenScope = new ReadOnlyDictionary<string, string[]>(
jsonResponse.GetPropertyValue("scope", string.Empty)
.Split(' ')
.Select(x => x.Split(new[] { ':' }, 2))
.Select(x => new KeyValuePair<string, string[]>(x[0], x.Skip(1).ToArray()))
.GroupBy(x => x.Key)
.OrderBy(x => x.Key)
.ToDictionary(
x => x.Key,
x => x.SelectMany(c => c.Value).OrderBy(c => c).ToArray()));
_connectionInfo.Scopes = new ReadOnlyCollection<string>(jsonResponse.GetPropertyValue("scope", string.Empty).Split(' ').OrderBy(x => x).ToList());

// Please note that Server-to-Server OAuth does not use the refresh token.
// Therefore change the grant type to 'RefreshToken' only when the response includes a refresh token.
Expand Down
21 changes: 21 additions & 0 deletions Source/ZoomNet/ZoomClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Pathoschild.Http.Client;
using Pathoschild.Http.Client.Extensibility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
Expand Down Expand Up @@ -244,6 +246,25 @@ private ZoomClient(IConnectionInfo connectionInfo, HttpClient httpClient, bool d

#region PUBLIC METHODS

/// <inheritdoc/>
public bool HasPermissions(IEnumerable<string> scopes)
{
var tokenHandler = _fluentClient.Filters.OfType<OAuthTokenHandler>().SingleOrDefault();
if (tokenHandler == null) throw new Exception("The concept of scopes only applies when using an OAuth connection.");

// Ensure the token (and by extension the scopes) is not expired
tokenHandler.RefreshTokenIfNecessary(false);

// The list of scopes can be empty if a previously issued token was specified when the OAuthConnectionInfo was instantiated.
// I am not aware of any way to fetch the list of scopes which would enable me to populate the list of scopes in the OAuthConnectionInfo.
// Therefore in this scenario the only workaround I can think of is to force the token to be refreshed.
var oAuthConnectionInfo = (OAuthConnectionInfo)tokenHandler.ConnectionInfo;
if (oAuthConnectionInfo.Scopes == null) tokenHandler.RefreshTokenIfNecessary(true); // Force the token to be refreshed wich will have the side-effect of populating the '.Scopes'

var missingScopes = scopes.Except(((OAuthConnectionInfo)tokenHandler.ConnectionInfo).Scopes).ToArray();
return !missingScopes.Any();
}

/// <inheritdoc/>
public void Dispose()
{
Expand Down

0 comments on commit 255a8aa

Please sign in to comment.