Skip to content

Commit

Permalink
Add Calls Graph support to Blazor WASM config (#1851)
Browse files Browse the repository at this point in the history
* Add Calls Graph support to Blazor WASM config

* Remove downstreamApi

* Store codefiles as .txt
  • Loading branch information
zahalzel authored Mar 23, 2022
1 parent b4c91ae commit 749e104
Show file tree
Hide file tree
Showing 15 changed files with 915 additions and 592 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Authentication.WebAssembly.Msal.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Graph;

/// <summary>
/// Adds services and implements methods to use Microsoft Graph SDK.
/// </summary>
internal static class GraphClientExtensions
{
/// <summary>
/// Extension method for adding the Microsoft Graph SDK to IServiceCollection.
/// </summary>
/// <param name="services"></param>
/// <param name="scopes">The MS Graph scopes to request</param>
/// <returns></returns>
public static IServiceCollection AddMicrosoftGraphClient(this IServiceCollection services, params string[] scopes)
{
services.Configure<RemoteAuthenticationOptions<MsalProviderOptions>>(options =>
{
foreach (var scope in scopes)
{
options.ProviderOptions.AdditionalScopesToConsent.Add(scope);
}
});

services.AddScoped<IAuthenticationProvider, GraphAuthenticationProvider>();
services.AddScoped<IHttpProvider, HttpClientHttpProvider>(sp => new HttpClientHttpProvider(new HttpClient()));
services.AddScoped(sp => new GraphServiceClient(
sp.GetRequiredService<IAuthenticationProvider>(),
sp.GetRequiredService<IHttpProvider>()));
return services;
}

/// <summary>
/// Implements IAuthenticationProvider interface.
/// Tries to get an access token for Microsoft Graph.
/// </summary>
private class GraphAuthenticationProvider : IAuthenticationProvider
{
public GraphAuthenticationProvider(IAccessTokenProvider provider)
{
Provider = provider;
}

public IAccessTokenProvider Provider { get; }

public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
var result = await Provider.RequestAccessToken(new AccessTokenRequestOptions()
{
Scopes = new[] { "https://graph.microsoft.com/User.Read" }
});

if (result.TryGetToken(out var token))
{
request.Headers.Authorization ??= new AuthenticationHeaderValue("Bearer", token.Value);
}
}
}

private class HttpClientHttpProvider : IHttpProvider
{
private readonly HttpClient _client;

public HttpClientHttpProvider(HttpClient client)
{
_client = client;
}

public ISerializer Serializer { get; } = new Serializer();

public TimeSpan OverallTimeout { get; set; } = TimeSpan.FromSeconds(300);

public void Dispose()
{
}

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
return _client.SendAsync(request);
}

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
{
return _client.SendAsync(request, completionOption, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@page "/profile"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Graph
@inject Microsoft.Graph.GraphServiceClient GraphServiceClient
@attribute [Authorize]

<h3>User Profile</h3>
@if (user == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Property</th>
<th>Value</th>
</tr>
</thead>
<tr>
<td> DisplayName </td>
<td> @user.DisplayName </td>
</tr>
<tr>
<td> UserPrincipalName </td>
<td> @user.UserPrincipalName </td>
</tr>
</table>
}

@code {
User? user;

protected override async Task OnInitializedAsync()
{
try
{
user = await GraphServiceClient.Me.Request().GetAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
"Methods": {
"Global": {
"CodeChanges": [
{
"Block": "builder.Services.AddMicrosoftGraphClient(\"https://graph.microsoft.com/User.Read\")",
"Options": [ "MicrosoftGraph" ],
"InsertAfter": "builder.Services.AddScoped",
"CodeFormatting": {
"Newline": true
}
},
{
"Block": "builder.Services.AddMsalAuthentication()",
"InsertAfter": "builder.Services.AddScoped",
Expand All @@ -23,6 +31,16 @@
"Newline": true,
"NumberOfSpaces": 4
}
},
{
"Block": "options.ProviderOptions.DefaultAccessTokenScopes.Add(\"https://graph.microsoft.com/User.Read\")",
"Options": [ "MicrosoftGraph" ],
"CodeChangeType": "Lambda",
"Parameter": "options",
"Parent": "builder.Services.AddMsalAuthentication",
"CodeFormatting": {
"NumberOfSpaces": 4
}
}
]
}
Expand Down Expand Up @@ -71,7 +89,7 @@
"AddFilePath": "Pages/Authentication.razor"
},
{
"FileName": "LoginDisplay.razor",
"FileName": "LoginDisplay.razor",
"AddFilePath": "Shared/LoginDisplay.razor"
},
{
Expand All @@ -86,6 +104,27 @@
{
"FileName": "RedirectToLogin.razor",
"AddFilePath": "Shared/RedirectToLogin.razor"
},
{
"FileName": "NavMenu.razor",
"Options": [ "MicrosoftGraph" ],
"Replacements": [
{
"Block": "</NavLink>\r\n </div>\r\n <div class=\"nav-item px-3\">\r\n <NavLink class=\"nav-link\" href=\"profile\">\r\n <span class=\"oi oi-list-rich\" aria-hidden=\"true\"></span> Show profile\r\n </NavLink>\r\n </div>\r\n </nav>\r\n</div>",
"ReplaceSnippet": "</NavLink>\r\n </div>\r\n </nav>\r\n</div>",
"Options": [ "MicrosoftGraph" ]
}
]
},
{
"FileName": "UserProfile.razor",
"AddFilePath": "Pages/UserProfile.razor",
"Options": [ "MicrosoftGraph" ]
},
{
"FileName": "GraphClientExtensions.cs",
"AddFilePath": "Data/GraphClientExtensions.cs",
"Options": [ "MicrosoftGraph" ]
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.DotNet.MSIdentity.Properties;
using Microsoft.DotNet.MSIdentity.Shared;
using Microsoft.DotNet.MSIdentity.Tool;
using Microsoft.DotNet.Scaffolding.Shared.CodeModifier;
Expand All @@ -23,6 +24,7 @@ internal class ProjectModifier
private readonly ProvisioningToolOptions _toolOptions;
private readonly IEnumerable<string> _files;
private readonly IConsoleLogger _consoleLogger;
private PropertyInfo? _codeModifierConfigPropertyInfo;

public ProjectModifier(ProvisioningToolOptions toolOptions, IEnumerable<string> files, IConsoleLogger consoleLogger)
{
Expand Down Expand Up @@ -110,9 +112,14 @@ private PropertyInfo? CodeModifierConfigPropertyInfo
{
get
{
var identifier = _toolOptions.ProjectTypeIdentifier.Replace('-', '_');
var propertyInfo = AppProvisioningTool.Properties.FirstOrDefault(p => p.Name.StartsWith("cm") && p.Name.EndsWith(identifier));
return propertyInfo;
if (_codeModifierConfigPropertyInfo == null)
{
var codeModifierName = $"cm_{_toolOptions.ProjectTypeIdentifier.Replace('-', '_')}";
_codeModifierConfigPropertyInfo = AppProvisioningTool.Properties.FirstOrDefault(
p => p.Name.Equals(codeModifierName));
}

return _codeModifierConfigPropertyInfo;
}
}

Expand Down Expand Up @@ -177,36 +184,32 @@ private void AddFile(CodeFile file, string identifier)
{
Directory.CreateDirectory(fileDir);
File.WriteAllText(filePath, codeFileString);
_consoleLogger.LogMessage($"Added {filePath}.\n");
}
}

private string GetCodeFileString(CodeFile file, string identifier)
internal static string GetCodeFileString(CodeFile file, string identifier) // todo make all code files strings
{
var propertyInfo = GetPropertyInfo(file.FileName, identifier);
if (propertyInfo is null)
// Resource files cannot contain '-' (dash) or '.' (period)
var codeFilePropertyName = $"add_{identifier.Replace('-', '_')}_{file.FileName.Replace('.', '_')}";
var property = AppProvisioningTool.Properties.FirstOrDefault(
p => p.Name.Equals(codeFilePropertyName));

if (property is null)
{
throw new FormatException($"Resource file for {file.FileName} could not be found. ");
throw new FormatException($"Resource property for {file.FileName} could not be found. ");
}

byte[] content = (propertyInfo.GetValue(null) as byte[])!;
string codeFileString = Encoding.UTF8.GetString(content);
var codeFileString = property.GetValue(typeof(Resources))?.ToString();

if (string.IsNullOrEmpty(codeFileString))
{
throw new FormatException($"Resource file for {file.FileName} could not be parsed. ");
throw new FormatException($"CodeFile string for {file.FileName} was empty.");
}

return codeFileString;
}

private PropertyInfo? GetPropertyInfo(string fileName, string identifier)
{
return AppProvisioningTool.Properties.Where(
p => p.Name.StartsWith("add")
&& p.Name.Contains(identifier.Replace('-', '_')) // Resource files cannot have '-' (dash character)
&& p.Name.Contains(fileName.Replace('.', '_'))) // Resource files cannot have '.' (period character)
.FirstOrDefault();
}

internal async Task ModifyCsFile(CodeFile file, CodeAnalysis.Project project, CodeChangeOptions options)
{
if (file.FileName.Equals("Startup.cs"))
Expand Down Expand Up @@ -249,7 +252,7 @@ internal async Task ModifyCsFile(CodeFile file, CodeAnalysis.Project project, Co
var root = documentBuilder.AddUsings(options);
if (file.FileName.Equals("Program.cs") && file.Methods.TryGetValue("Global", out var globalChanges))
{

var filteredChanges = ProjectModifierHelper.FilterCodeSnippets(globalChanges.CodeChanges, options);
var updatedIdentifer = ProjectModifierHelper.GetBuilderVariableIdentifierTransformation(root.Members);
if (updatedIdentifer.HasValue)
Expand Down Expand Up @@ -281,7 +284,7 @@ node is ClassDeclarationSyntax cds &&
modifiedClassDeclarationSyntax = documentBuilder.AddClassAttributes(modifiedClassDeclarationSyntax, options);

modifiedClassDeclarationSyntax = ModifyMethods(modifiedClassDeclarationSyntax, documentBuilder, file.Methods, options);

//add code snippets/changes.

//replace class node with all the updates.
Expand Down
Loading

0 comments on commit 749e104

Please sign in to comment.