diff --git a/GraphExplorerAppModeService/GraphExplorerAppModeService.csproj b/GraphExplorerAppModeService/GraphExplorerAppModeService.csproj index 522ad66e5..52b8f98f0 100644 --- a/GraphExplorerAppModeService/GraphExplorerAppModeService.csproj +++ b/GraphExplorerAppModeService/GraphExplorerAppModeService.csproj @@ -8,5 +8,7 @@ + + diff --git a/GraphExplorerAppModeService/Interfaces/IGraphAppAuthProvider.cs b/GraphExplorerAppModeService/Interfaces/IGraphAppAuthProvider.cs index 669e3feeb..56198c228 100644 --- a/GraphExplorerAppModeService/Interfaces/IGraphAppAuthProvider.cs +++ b/GraphExplorerAppModeService/Interfaces/IGraphAppAuthProvider.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Graph; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,7 +7,8 @@ namespace GraphExplorerAppModeService.Interfaces { - interface IGraphAppAuthProvider + public interface IGraphAppAuthProvider { + GraphServiceClient GetAuthenticatedGraphClient(string accessToken); } } diff --git a/GraphExplorerAppModeService/Services/GraphAppAuthProvider.cs b/GraphExplorerAppModeService/Services/GraphAppAuthProvider.cs index 67f55c01a..877ebea9c 100644 --- a/GraphExplorerAppModeService/Services/GraphAppAuthProvider.cs +++ b/GraphExplorerAppModeService/Services/GraphAppAuthProvider.cs @@ -1,8 +1,11 @@ using GraphExplorerAppModeService.Interfaces; using Microsoft.Extensions.Configuration; +using Microsoft.Graph; +using Microsoft.Identity.Web; using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; @@ -11,9 +14,14 @@ namespace GraphExplorerAppModeService.Services { public class GraphAppAuthProvider : IGraphAppAuthProvider { - public GraphAppAuthProvider(IConfiguration configuration) - { - } + public GraphServiceClient GetAuthenticatedGraphClient(string accessToken) => + new GraphServiceClient(new DelegateAuthenticationProvider( + async requestMessage => + { + // Append the access token to the request + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + })); } + } diff --git a/GraphWebApi/Controllers/GraphExplorerAppModeController.cs b/GraphWebApi/Controllers/GraphExplorerAppModeController.cs index 01ea80cae..4d311b3b9 100644 --- a/GraphWebApi/Controllers/GraphExplorerAppModeController.cs +++ b/GraphWebApi/Controllers/GraphExplorerAppModeController.cs @@ -2,49 +2,62 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; -using GraphExplorerAppModeService.Interfaces; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Identity.Web; using System.Net.Http; using System.Net.Http.Headers; +using Microsoft.Graph; +using System.Net; +using Microsoft.Identity.Client; +using System.Threading; +using Microsoft.Extensions.Primitives; +using GraphExplorerAppModeService.Services; +using GraphExplorerAppModeService.Interfaces; +using Microsoft.Extensions.Configuration; namespace GraphWebApi.Controllers { [ApiController] public class GraphExplorerAppModeController : ControllerBase { - private readonly ITokenAcquisition tokenAcquisition; - public GraphExplorerAppModeController(ITokenAcquisition tokenAcquisition) + private readonly IGraphAppAuthProvider _graphClient; + private readonly ITokenAcquisition _tokenAcquisition; + + private readonly IConfiguration _config; + + public GraphExplorerAppModeController(IConfiguration configuration, ITokenAcquisition tokenAcquisition, IGraphAppAuthProvider graphServiceClient) { - this.tokenAcquisition = tokenAcquisition; + this._graphClient = graphServiceClient; + this._tokenAcquisition = tokenAcquisition; + this._config = configuration; } [Route("api/[controller]/{*all}")] [Route("graphproxy/{*all}")] [HttpGet] - public async Task GetAsync(string all, [FromHeader] string Authorization) + [AuthorizeForScopes(Scopes = new[] { "https://graph.microsoft.com/.default" })] + public async Task GetAsync(string all) { - return await ProcessRequestAsync("GET", all, null, Authorization).ConfigureAwait(false); + return await this.ProcessRequestAsync("GET", all, null, _config.GetSection("AzureAd").GetSection("TenantId").Value).ConfigureAwait(false); } - [Route("api/[controller]/{*all}")] - [Route("graphproxy/token/{tenantId}")] - [HttpGet] - [AuthorizeForScopes(Scopes = new[] { "https://graph.microsoft.com/.default" })] - public async Task GetTokenAsync(string tenantId) + private async Task GetTokenAsync(string tenantId) { // Acquire the access token. string scopes = "https://graph.microsoft.com/.default"; - return await tokenAcquisition.GetAccessTokenForAppAsync(scopes, tenantId, null); + + return await _tokenAcquisition.GetAccessTokenForAppAsync(scopes, tenantId, null); } + [Route("api/[controller]/{*all}")] [Route("graphproxy/{*all}")] [HttpPost] - public async Task PostAsync(string all, [FromBody] object body, [FromHeader] string Authorization) + [AuthorizeForScopes(Scopes = new[] { "https://graph.microsoft.com/.default" })] + public async Task PostAsync(string all, [FromBody] object body) { - return await ProcessRequestAsync("POST", all, body, Authorization).ConfigureAwait(false); + return await ProcessRequestAsync("POST", all, body, _config.GetSection("AzureAd").GetSection("TenantId").Value).ConfigureAwait(false); } [Route("api/[controller]/{*all}")] @@ -52,29 +65,122 @@ public async Task PostAsync(string all, [FromBody] object body, [ [HttpDelete] public async Task DeleteAsync(string all, [FromHeader] string Authorization) { - return await ProcessRequestAsync("DELETE", all, null, Authorization).ConfigureAwait(false); + return await ProcessRequestAsync("DELETE", all, null, _config.GetSection("AzureAd").GetSection("TenantId").Value).ConfigureAwait(false); } [Route("api/[controller]/{*all}")] [Route("graphproxy/{*all}")] [HttpPut] - public async Task PutAsync(string all, [FromBody] object body, [FromHeader] string Authorization) + [AuthorizeForScopes(Scopes = new[] { "https://graph.microsoft.com/.default" })] + public async Task PutAsync(string all, [FromBody] object body) + { + return await ProcessRequestAsync("PUT", all, body, _config.GetSection("AzureAd").GetSection("TenantId").Value).ConfigureAwait(false); + } + + private string GetBaseUrlWithoutVersion(GraphServiceClient graphClient) + { + var baseUrl = graphClient.BaseUrl; + var index = baseUrl.LastIndexOf('/'); + return baseUrl.Substring(0, index); + } + + public class HttpResponseMessageResult : IActionResult { - return await ProcessRequestAsync("PUT", all, body, Authorization).ConfigureAwait(false); + private readonly HttpResponseMessage _responseMessage; + + public HttpResponseMessageResult(HttpResponseMessage responseMessage) + { + _responseMessage = responseMessage; // could add throw if null + } + + public async Task ExecuteResultAsync(ActionContext context) + { + context.HttpContext.Response.StatusCode = (int)_responseMessage.StatusCode; + + foreach (var header in _responseMessage.Headers) + { + context.HttpContext.Response.Headers.TryAdd(header.Key, new StringValues(header.Value.ToArray())); + } + + context.HttpContext.Response.ContentType = _responseMessage.Content.Headers.ContentType.ToString(); + + using (var stream = await _responseMessage.Content.ReadAsStreamAsync()) + { + await stream.CopyToAsync(context.HttpContext.Response.Body); + await context.HttpContext.Response.Body.FlushAsync(); + } + } } [Route("api/[controller]/{*all}")] [Route("graphproxy/{*all}")] [HttpPatch] - public async Task PatchAsync(string all, [FromBody] object body, [FromHeader] string Authorization) + [AuthorizeForScopes(Scopes = new[] { "https://graph.microsoft.com/.default" })] + public async Task PatchAsync(string all, [FromBody] object body) + { + return await ProcessRequestAsync("PATCH", all, body, _config.GetSection("AzureAd").GetSection("TenantId").Value).ConfigureAwait(false); + } + + private async Task ProcessRequestAsync(string method, string all, object content, string tenantId) { - return await ProcessRequestAsync("PATCH", all, body, Authorization).ConfigureAwait(false); + GraphServiceClient _graphServiceClient = _graphClient.GetAuthenticatedGraphClient(GetTokenAsync(tenantId).Result.ToString()); + + var qs = HttpContext.Request.QueryString; + + var url = $"{GetBaseUrlWithoutVersion(_graphServiceClient)}/{all}{qs.ToUriComponent()}"; + + var request = new BaseRequest(url, _graphServiceClient, null) + { + Method = method, + ContentType = HttpContext.Request.ContentType, + }; + + var neededHeaders = Request.Headers.Where(h => h.Key.ToLower() == "if-match" || h.Key.ToLower() == "consistencylevel").ToList(); + if (neededHeaders.Count() > 0) + { + foreach (var header in neededHeaders) + { + request.Headers.Add(new HeaderOption(header.Key, string.Join(",", header.Value))); + } + } + + var contentType = "application/json"; + try + { + using (var response = await request.SendRequestAsync(content?.ToString(), CancellationToken.None, HttpCompletionOption.ResponseContentRead).ConfigureAwait(false)) + { + response.Content.Headers.TryGetValues("content-type", out var contentTypes); + + contentType = contentTypes?.FirstOrDefault() ?? contentType; + + var byteArrayContent = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + Console.WriteLine(byteArrayContent); + return new HttpResponseMessageResult(ReturnHttpResponseMessage(HttpStatusCode.OK, contentType, new ByteArrayContent(byteArrayContent))); + } + } + catch (ServiceException ex) + { + return new HttpResponseMessageResult(ReturnHttpResponseMessage(ex.StatusCode, contentType, new StringContent(ex.Error.ToString()))); + } } - private async Task ProcessRequestAsync(string method, string all, object content, string Authorizaton) + private static HttpResponseMessage ReturnHttpResponseMessage(HttpStatusCode httpStatusCode, string contentType, HttpContent httpContent) { - - return Ok(); + var httpResponseMessage = new HttpResponseMessage(httpStatusCode) + { + Content = httpContent + }; + + try + { + httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType); + } + catch + { + httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + } + + return httpResponseMessage; } } } diff --git a/GraphWebApi/GraphWebApi.csproj b/GraphWebApi/GraphWebApi.csproj index 99e0191a5..20b3fd145 100644 --- a/GraphWebApi/GraphWebApi.csproj +++ b/GraphWebApi/GraphWebApi.csproj @@ -49,10 +49,12 @@ + + diff --git a/GraphWebApi/Startup.cs b/GraphWebApi/Startup.cs index f14f69a6d..342d1aab5 100644 --- a/GraphWebApi/Startup.cs +++ b/GraphWebApi/Startup.cs @@ -30,6 +30,8 @@ using System.Threading.Tasks; using Microsoft.Identity.Web; using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using GraphExplorerAppModeService.Services; +using GraphExplorerAppModeService.Interfaces; namespace GraphWebApi { @@ -51,6 +53,7 @@ public void ConfigureServices(IServiceCollection services) services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(Configuration, "AzureAd") .EnableTokenAcquisitionToCallDownstreamApi() + .AddMicrosoftGraph(Configuration.GetSection("GraphV1")) .AddInMemoryTokenCaches(); services.AddDistributedMemoryCache(); services.AddAuthentication(option => @@ -103,6 +106,7 @@ public void ConfigureServices(IServiceCollection services) services.AddMemoryCache(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton();