From 00086af8c46c3ed28ed62cd06c729d81d25087a7 Mon Sep 17 00:00:00 2001 From: jennyf19 Date: Mon, 25 Jan 2021 12:12:54 -0800 Subject: [PATCH] Add graph & api call to worker2 (#901) * initial commit to add graph & api call to worker2 * remove renamed file * more updates to worker2 * update test templates * fix usings * one more using * move api calls to greeterService * fix variable name * remove space * fix helloReply --- .../.template.config/dotnetcli.host.json | 14 +++- .../.template.config/template.json | 35 +++++++- .../Worker-CSharp/Company.Application1.csproj | 4 +- .../templates/Worker-CSharp/GreeterService.cs | 81 +++++++++++++++++-- .../templates/Worker-CSharp/Startup.cs | 27 ++++++- .../test-templates-from-nuget.bat | 15 ++++ ProjectTemplates/test-templates.bat | 14 ++++ 7 files changed, 177 insertions(+), 13 deletions(-) diff --git a/ProjectTemplates/templates/Worker-CSharp/.template.config/dotnetcli.host.json b/ProjectTemplates/templates/Worker-CSharp/.template.config/dotnetcli.host.json index 665f91c53..a7609133d 100644 --- a/ProjectTemplates/templates/Worker-CSharp/.template.config/dotnetcli.host.json +++ b/ProjectTemplates/templates/Worker-CSharp/.template.config/dotnetcli.host.json @@ -41,7 +41,19 @@ "shortName": "" }, "ExcludeLaunchSettings": { - "longName": "exclude-launch-settings", + "longName": "exclude-launch-settings", + "shortName": "" + }, + "CalledApiUrl": { + "longName": "called-api-url", + "shortName": "" + }, + "CalledApiScopes": { + "longName": "called-api-scopes", + "shortName": "" + }, + "CallsMicrosoftGraph": { + "longName": "calls-graph", "shortName": "" } }, diff --git a/ProjectTemplates/templates/Worker-CSharp/.template.config/template.json b/ProjectTemplates/templates/Worker-CSharp/.template.config/template.json index 73e7ca7dc..a27e13529 100644 --- a/ProjectTemplates/templates/Worker-CSharp/.template.config/template.json +++ b/ProjectTemplates/templates/Worker-CSharp/.template.config/template.json @@ -160,10 +160,41 @@ } }, "skipRestore": { + "type": "parameter", + "datatype": "bool", + "description": "If specified, skips the automatic restore of the project on create.", + "defaultValue": "false" + }, + "CalledApiUrl": { + "type": "parameter", + "datatype": "string", + "replaces": "[WebApiUrl]", + "defaultValue": "https://graph.microsoft.com/v1.0", + "description": "URL of the API to call from the web app. This option only applies if --auth SingleOrg or --auth IndividualB2C is specified." + }, + "CallsMicrosoftGraph": { "type": "parameter", "datatype": "bool", - "description": "If specified, skips the automatic restore of the project on create.", - "defaultValue": "false" + "defaultValue": "false", + "description": "Specifies if the web app calls Microsoft Graph. This option only applies if --auth SingleOrg is specified." + }, + "CalledApiScopes": { + "type": "parameter", + "datatype": "string", + "replaces": "user.read", + "description": "Scopes to request to call the API from the web app. This option only applies if --auth SingleOrg or --auth IndividualB2C is specified." + }, + "GenerateApi": { + "type": "computed", + "value": "((IndividualB2CAuth || OrganizationalAuth) && (CalledApiUrl != \"https://graph.microsoft.com/v1.0\" || CalledApiScopes != \"user.read\"))" + }, + "GenerateGraph": { + "type": "computed", + "value": "(OrganizationalAuth && CallsMicrosoftGraph)" + }, + "GenerateApiOrGraph": { + "type": "computed", + "value": "(GenerateApi || GenerateGraph)" } }, "primaryOutputs": [ diff --git a/ProjectTemplates/templates/Worker-CSharp/Company.Application1.csproj b/ProjectTemplates/templates/Worker-CSharp/Company.Application1.csproj index 5d4ffb545..e8e1a6ea5 100644 --- a/ProjectTemplates/templates/Worker-CSharp/Company.Application1.csproj +++ b/ProjectTemplates/templates/Worker-CSharp/Company.Application1.csproj @@ -13,7 +13,9 @@ - + + + diff --git a/ProjectTemplates/templates/Worker-CSharp/GreeterService.cs b/ProjectTemplates/templates/Worker-CSharp/GreeterService.cs index f17f0825d..4cc6a03a6 100644 --- a/ProjectTemplates/templates/Worker-CSharp/GreeterService.cs +++ b/ProjectTemplates/templates/Worker-CSharp/GreeterService.cs @@ -2,9 +2,19 @@ using Grpc.Core; #if (!NoAuth) using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +#endif +#if (GenerateApi) +using Microsoft.Extensions.Configuration; +using Microsoft.Identity.Web; +using System.Net; +using System.Net.Http; +#endif +#if (GenerateGraph) +using Microsoft.Graph; #endif using Microsoft.Extensions.Logging; -#if (OrganizationalAuth || IndividualB2CAuth) +#if (!NoAuth) using Microsoft.Identity.Web.Resource; #endif @@ -13,26 +23,87 @@ namespace Company.Application1 public class GreeterService : Greeter.GreeterBase { private readonly ILogger _logger; - public GreeterService(ILogger logger) + +#if (!NoAuth) + // The web API will only accept tokens 1) for users, and 2) having the "api-scope" scope for this API + static readonly string[] scopeRequiredByApi = new string[] { "api-scope" }; +#endif + +#if (GenerateApi) + private readonly IDownstreamWebApi _downstreamWebApi; + + public GreeterService(ILogger logger, + IDownstreamWebApi downstreamWebApi) { _logger = logger; + _downstreamWebApi = downstreamWebApi; } - static string[] scopeRequiredByAPI = new string[] { "access_as_user" }; + [Authorize] + public override async Task SayHello(HelloRequest request, ServerCallContext context) + { + var httpContext = context.GetHttpContext(); + httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); + using var response = await _downstreamWebApi.CallWebApiForUserAsync("DownstreamApi").ConfigureAwait(false); + if (response.StatusCode == System.Net.HttpStatusCode.OK) + { + var apiResult = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + // Do something with apiResult + } + else + { + var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new HttpRequestException($"Invalid status code in the HttpResponseMessage: {response.StatusCode}: {error}"); + } + + return new HelloReply() + { + Message = "Hello " + request.Name + }; + } + +#elseif (GenerateGraph) + private readonly GraphServiceClient _graphServiceClient; + + public GreeterService(ILogger logger, + GraphServiceClient graphServiceClient) + { + _logger = logger; + _graphServiceClient = graphServiceClient; + } + + [Authorize] + public override async Task SayHello(HelloRequest request, ServerCallContext context) + { + var httpContext = context.GetHttpContext(); + httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); + var user = await _graphServiceClient.Me.Request().GetAsync(); + + return new HelloReply() + { + Message = "Hello " + user.DisplayName + }; + } +#else + public GreeterService(ILogger logger) + { + _logger = logger; + } #if (!NoAuth) [Authorize] #endif public override Task SayHello(HelloRequest request, ServerCallContext context) { -#if (OrganizationalAuth || IndividualB2CAuth) +#if (!NoAuth) var httpContext = context.GetHttpContext(); - httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByAPI); + httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); #endif return Task.FromResult(new HelloReply { Message = "Hello " + request.Name }); } +#endif } } diff --git a/ProjectTemplates/templates/Worker-CSharp/Startup.cs b/ProjectTemplates/templates/Worker-CSharp/Startup.cs index b9a3bfd53..57e20cb21 100644 --- a/ProjectTemplates/templates/Worker-CSharp/Startup.cs +++ b/ProjectTemplates/templates/Worker-CSharp/Startup.cs @@ -9,6 +9,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.AspNetCore.Http; +#if (GenerateGraph) +using Microsoft.Graph; +#endif namespace Company.Application1 { @@ -28,15 +31,31 @@ public void ConfigureServices(IServiceCollection services) #if (OrganizationalAuth) services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) +#if (GenerateApiOrGraph) + .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd")) + .EnableTokenAcquisitionToCallDownstreamApi() +#if (GenerateApi) + .AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi")) +#endif +#if (GenerateGraph) + .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi")) +#endif + .AddInMemoryTokenCaches(); +#else .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd")); - - services.AddAuthorization(); +#endif #elif (IndividualB2CAuth) services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) +#if (GenerateApi) + .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C")) + .EnableTokenAcquisitionToCallDownstreamApi() + .AddDownstreamWebApi("DownstreamApi", Configuration.GetSection("DownstreamApi")) + .AddInMemoryTokenCaches(); +#else .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAdB2C")); - - services.AddAuthorization(); #endif +#endif + services.AddAuthorization(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/ProjectTemplates/test-templates-from-nuget.bat b/ProjectTemplates/test-templates-from-nuget.bat index 74560b0b9..ce9c8200d 100644 --- a/ProjectTemplates/test-templates-from-nuget.bat +++ b/ProjectTemplates/test-templates-from-nuget.bat @@ -305,6 +305,21 @@ cd worker2-b2c dotnet new worker2 --auth IndividualB2C dotnet sln ..\..\tests.sln add worker2-b2c.csproj cd .. + +echo "Test worker2, single-org, calling microsoft graph" +mkdir worker2-singleorg-callsgraph +cd worker2-singleorg-callsgraph +dotnet new worker2 --auth SingleOrg --calls-graph +dotnet sln ..\..\tests.sln add worker2-singleorg-callsgraph.csproj +cd .. + +echo "Test worker2, single-org, calling a downstream web api" +mkdir worker2-singleorg-callswebapi +cd worker2-singleorg-callswebapi +dotnet new worker2 --auth SingleOrg --called-api-url "https://graph.microsoft.com/beta/me" --called-api-scopes "user.read" +dotnet sln ..\..\tests.sln add worker2-singleorg-callswebapi.csproj +cd .. + cd .. echo "Configure the applications" diff --git a/ProjectTemplates/test-templates.bat b/ProjectTemplates/test-templates.bat index 467899696..03061ed30 100644 --- a/ProjectTemplates/test-templates.bat +++ b/ProjectTemplates/test-templates.bat @@ -302,6 +302,20 @@ dotnet new worker2 --auth IndividualB2C dotnet sln ..\..\tests.sln add worker2-b2c.csproj cd .. +echo "Test worker2, single-org, calling microsoft graph" +mkdir worker2-singleorg-callsgraph +cd worker2-singleorg-callsgraph +dotnet new worker2 --auth SingleOrg --calls-graph +dotnet sln ..\..\tests.sln add worker2-singleorg-callsgraph.csproj +cd .. + +echo "Test worker2, single-org, calling a downstream web api" +mkdir worker2-singleorg-callswebapi +cd worker2-singleorg-callswebapi +dotnet new worker2 --auth SingleOrg --called-api-url "https://graph.microsoft.com/beta/me" --called-api-scopes "user.read" +dotnet sln ..\..\tests.sln add worker2-singleorg-callswebapi.csproj +cd .. + cd .. echo "Configure the applications"