Skip to content

Commit

Permalink
[Update] Remove SmallsOnline.MsGraphClient dependency (#54)
Browse files Browse the repository at this point in the history
* Add classes for configuring the GraphClient

* Remove references to SmallsOnline.MsGraphClient

GraphClientService no longers uses this.

* Update XML doc text

* Remove SmallsOnline.MsGraphClient package

* Add methods for authenticating the service

* Add method for sending API calls to MS Graph

* Restructure files / update API call implementation

* Update GraphClientService config in AdminConsole

* Add logic for when "Show users" is null

* Update GraphClientService config in FunctionApp

* Make ConnectAsync private

* Make GetAuthTokenAsync private

* Add XML docs to new methods
  • Loading branch information
Smalls1652 authored Jul 10, 2023
1 parent d2353f2 commit b513861
Show file tree
Hide file tree
Showing 17 changed files with 317 additions and 38 deletions.
17 changes: 14 additions & 3 deletions src/AdminConsole/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
using SmallsOnline.PasswordExpirationNotifier.Lib.Models.Graph;
using SmallsOnline.PasswordExpirationNotifier.Lib.Services;

var builder = WebApplication.CreateBuilder(args);
Expand Down Expand Up @@ -45,9 +46,19 @@
builder.Services
.AddSingleton<IGraphClientService, GraphClientService>(
provider => new(
clientId: builder.Configuration.GetSection("backendClientID").Value!,
clientSecret: builder.Configuration.GetSection("backendClientSecret").Value!,
tenantId: builder.Configuration.GetSection("backendTenantId").Value!
config: new()
{
ClientId = builder.Configuration.GetSection("backendClientId").Value!,
TenantId = builder.Configuration.GetSection("backendTenantId").Value!,
Credential = new GraphClientCredential(
credentialType: GraphClientCredentialType.ClientSecret,
clientSecret: builder.Configuration.GetSection("backendClientSecret").Value!
),
ApiScopes = new[]
{
"https://graph.microsoft.com/.default"
}
}
)
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,16 @@
</div>
</div>
}
else if (_users is null)
{
<div class="row">
<div class="col">
<p>
Click the button above to get users.
</p>
</div>
</div>
}
else
{
<div class="row">
Expand Down
17 changes: 14 additions & 3 deletions src/FunctionApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.Extensions.Hosting;
using SmallsOnline.PasswordExpirationNotifier.FunctionApp;
using SmallsOnline.PasswordExpirationNotifier.FunctionApp.Services;
using SmallsOnline.PasswordExpirationNotifier.Lib.Models.Graph;
using SmallsOnline.PasswordExpirationNotifier.Lib.Services;

IHostBuilder hostBuilder = new HostBuilder()
Expand All @@ -20,9 +21,19 @@
{
services.AddSingleton<IGraphClientService, GraphClientService>(
provider => new GraphClientService(
clientId: AppSettingsHelper.GetSettingValue("clientId")!,
clientSecret: AppSettingsHelper.GetSettingValue("clientSecret")!,
tenantId: AppSettingsHelper.GetSettingValue("tenantId")!
config: new()
{
ClientId = AppSettingsHelper.GetSettingValue("clientId")!,
TenantId = AppSettingsHelper.GetSettingValue("tenantId")!,
Credential = new GraphClientCredential(
credentialType: GraphClientCredentialType.ClientSecret,
clientSecret: AppSettingsHelper.GetSettingValue("clientSecret")!
),
ApiScopes = new[]
{
"https://graph.microsoft.com/.default"
}
}
)
);
Expand Down
4 changes: 1 addition & 3 deletions src/Lib/Lib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.35.1">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="SmallsOnline.MsGraphClient" Version="2022.8.0-beta04">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Identity.Client" Version="4.54.1" />
</ItemGroup>

</Project>
19 changes: 19 additions & 0 deletions src/Lib/Models/Graph/GraphClientConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace SmallsOnline.PasswordExpirationNotifier.Lib.Models.Graph;

/// <summary>
/// Holds the configuration for the Microsoft Graph API client used in <see cref="SmallsOnline.PasswordExpirationNotifier.Lib.Services.GraphClientService" />.
/// </summary>
public class GraphClientConfig : IGraphClientConfig
{
/// <inheritdoc />
public string ClientId { get; set; } = null!;

/// <inheritdoc />
public string TenantId { get; set; } = null!;

/// <inheritdoc />
public string[] ApiScopes { get; set; } = null!;

/// <inheritdoc />
public IGraphClientCredential Credential { get; set; } = null!;
}
30 changes: 30 additions & 0 deletions src/Lib/Models/Graph/GraphClientCredential.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Security.Cryptography.X509Certificates;

namespace SmallsOnline.PasswordExpirationNotifier.Lib.Models.Graph;

/// <summary>
/// Holds the credentials for authenticating with the Microsoft Graph API.
/// </summary>
public class GraphClientCredential : IGraphClientCredential
{
public GraphClientCredential(GraphClientCredentialType credentialType, string clientSecret)
{
CredentialType = credentialType;
ClientSecret = clientSecret;
}

public GraphClientCredential(GraphClientCredentialType credentialType, X509Certificate2 clientCertificate)
{
CredentialType = credentialType;
ClientCertificate = clientCertificate;
}

/// <inheritdoc />
public GraphClientCredentialType CredentialType { get; }

/// <inheritdoc />
public string? ClientSecret { get; }

/// <inheritdoc />
public X509Certificate2? ClientCertificate { get; }
}
17 changes: 17 additions & 0 deletions src/Lib/Models/Graph/GraphClientCredentialType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace SmallsOnline.PasswordExpirationNotifier.Lib.Models.Graph;

/// <summary>
/// The type of credential to use for authenticating an Azure AD app with the Microsoft Graph API.
/// </summary>
public enum GraphClientCredentialType
{
/// <summary>
/// The app uses a client secret for authentication.
/// </summary>
ClientSecret,

/// <summary>
/// The app uses a certificate for authentication.
/// </summary>
ClientCertificate
}
27 changes: 27 additions & 0 deletions src/Lib/Models/Graph/interfaces/IGraphClientConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace SmallsOnline.PasswordExpirationNotifier.Lib.Models.Graph;

/// <summary>
/// Interface for configuring the Microsoft Graph client in <see cref="SmallsOnline.PasswordExpirationNotifier.Lib.Services.GraphClientService"/>.
/// </summary>
public interface IGraphClientConfig
{
/// <summary>
/// The client ID of the Azure AD app.
/// </summary>
string ClientId { get; set; }

/// <summary>
/// The tenant ID of the Azure AD app.
/// </summary>
string TenantId { get; set; }

/// <summary>
/// The API scopes to request.
/// </summary>
string[] ApiScopes { get; set; }

/// <summary>
/// The credential to use for authenticating with the Microsoft Graph API.
/// </summary>
IGraphClientCredential Credential { get; set; }
}
24 changes: 24 additions & 0 deletions src/Lib/Models/Graph/interfaces/IGraphClientCredential.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Security.Cryptography.X509Certificates;

namespace SmallsOnline.PasswordExpirationNotifier.Lib.Models.Graph;

/// <summary>
/// Interface for holding credentials for authenticating with the Microsoft Graph API.
/// </summary>
public interface IGraphClientCredential
{
/// <summary>
/// The type of the credential.
/// </summary>
GraphClientCredentialType CredentialType { get; }

/// <summary>
/// The client secret for the app.
/// </summary>
string? ClientSecret { get; }

/// <summary>
/// The certificate for the app.
/// </summary>
X509Certificate2? ClientCertificate { get; }
}
37 changes: 23 additions & 14 deletions src/Lib/Services/GraphClientService/GraphClientService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.RegularExpressions;
using SmallsOnline.MsGraphClient.Models;
using Microsoft.Identity.Client;
using SmallsOnline.PasswordExpirationNotifier.Lib.Models.Graph;

namespace SmallsOnline.PasswordExpirationNotifier.Lib.Services;

Expand All @@ -8,7 +9,9 @@ namespace SmallsOnline.PasswordExpirationNotifier.Lib.Services;
/// </summary>
public partial class GraphClientService : IGraphClientService
{
private readonly GraphClient _graphClient;
private readonly IEnumerable<string> _apiScopes;
private readonly HttpClient _graphClient;
private readonly IConfidentialClientApplication _confidentialClientApplication;
private readonly JsonSourceGenerationContext _jsonSourceGenerationContext = new();
private readonly string[] _graphUserProps = new[]
{
Expand All @@ -22,22 +25,28 @@ public partial class GraphClientService : IGraphClientService
"onPremisesDistinguishedName"
};

public GraphClientService(string clientId, string tenantId, string clientSecret)
public GraphClientService(GraphClientConfig config)
{
_graphClient = new(
baseUri: new("https://graph.microsoft.com/beta/"),
clientId: clientId,
tenantId: tenantId,
credentialType: GraphClientCredentialType.Secret,
clientSecret: clientSecret,
apiScopes: new ApiScopesConfig(new[] { "https://graph.microsoft.com/.default" })
);

_graphClient.ConnectClient();
_apiScopes = config.ApiScopes;

_confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(config.ClientId)
.WithTenantId(config.TenantId)
.WithClientSecret(config.Credential.ClientSecret!)
.Build();

_graphClient = new()
{
BaseAddress = new Uri("https://graph.microsoft.com/beta/")
};
_graphClient.DefaultRequestHeaders.Add("ConsistencyLevel", "eventual");
}

/// <inheritdoc />
public GraphClient GraphClient => _graphClient;
public HttpClient GraphClient => _graphClient;

private bool _isConnected => _authToken is not null;
private AuthenticationResult? _authToken;

[GeneratedRegex("^https:\\/\\/graph.microsoft.com\\/(?'version'v1\\.0|beta)\\/(?'endpoint'.+?)$")]
private partial Regex _nextLinkRegex();
Expand Down
30 changes: 30 additions & 0 deletions src/Lib/Services/GraphClientService/authentication/ConnectAsync.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace SmallsOnline.PasswordExpirationNotifier.Lib.Services;

public partial class GraphClientService
{
/// <summary>
/// Connects to the Graph API and/or refreshes the authentication token if necessary.
/// </summary>
private async Task ConnectAsync()
{
// Invert the current value of _isConnected to determine if we need to connect.
bool needsToConnect = !_isConnected;

// If we already have an authentication token, check if it's expired.
// If it is, we need to set the value for 'needsToConnect' to true to get a new token.
if (_authToken is not null)
{
if (DateTimeOffset.Now >= _authToken.ExpiresOn)
{
needsToConnect = true;
}
}

// If needed, get a new authentication token to connect
// to the Graph API.
if (needsToConnect)
{
_authToken = await GetAuthTokenAsync();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Microsoft.Identity.Client;
using SmallsOnline.PasswordExpirationNotifier.Lib.Models.Graph;

namespace SmallsOnline.PasswordExpirationNotifier.Lib.Services;

public partial class GraphClientService
{
/// <summary>
/// Get an authentication token to connect to the Graph API.
/// </summary>
/// <returns><see cref="AuthenticationResult" /></returns>
private async Task<AuthenticationResult> GetAuthTokenAsync()
{
AuthenticationResult? authToken = await _confidentialClientApplication
.AcquireTokenForClient(_apiScopes)
.ExecuteAsync();

return authToken;
}
}
Loading

0 comments on commit b513861

Please sign in to comment.