From d289ec00cf58489982d656aa4d21918da2066a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Wed, 12 Jul 2017 12:10:10 +0200 Subject: [PATCH] Add swagger authorization option in APIs --- docker-compose.override.yml | 12 +++- docker-compose.yml | 1 + .../Basket/Basket.API/Basket.API.csproj | 2 +- .../Controllers/BasketController.cs | 2 +- .../Filters/AuthorizeCheckOperationFilter.cs | 32 ++++++++++ src/Services/Basket/Basket.API/Startup.cs | 30 +++++++--- .../Identity.API/Configuration/Config.cs | 60 +++++++++++++++++++ src/Services/Identity/Identity.API/Startup.cs | 4 ++ .../Location/Locations.API/Dockerfile | 2 +- .../Filters/AuthorizeCheckOperationFilter.cs | 2 +- .../Location/Locations.API/Startup.cs | 6 +- .../Filters/AuthorizeCheckOperationFilter.cs | 32 ++++++++++ .../Marketing/Marketing.API/Startup.cs | 18 +++++- .../Filters/AuthorizeCheckOperationFilter.cs | 32 ++++++++++ src/Services/Ordering/Ordering.API/Startup.cs | 18 ++++++ .../Payment.API/Controllers/HomeController.cs | 2 +- src/Services/Payment/Payment.API/Dockerfile | 2 +- 17 files changed, 238 insertions(+), 19 deletions(-) create mode 100644 src/Services/Basket/Basket.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs create mode 100644 src/Services/Marketing/Marketing.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs create mode 100644 src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 8d0880f92..1095691dc 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -20,6 +20,7 @@ services: - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=${ESHOP_AZURE_REDIS_BASKET_DB:-basket.data} - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110. + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq} ports: - "5103:80" @@ -44,7 +45,11 @@ services: - SpaClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5104 - XamarinCallback=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105/xamarincallback #localhost do not work for UWP login, so we have to use "external" IP always - ConnectionStrings__DefaultConnection=${ESHOP_AZURE_IDENTITY_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word} - - MvcClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your local dev-machine firewall at range 5100-5110. + - MvcClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5100 #Local: You need to open your local dev-machine firewall at range 5100-5110. + - LocationApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5109 + - MarketingApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110 + - BasketApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103 + - OrderingApiClient=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102 - UseCustomizationData=True ports: - "5105:80" @@ -55,6 +60,7 @@ services: - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=${ESHOP_AZURE_ORDERING_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word} - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110. + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq} - UseCustomizationData=True ports: @@ -69,6 +75,7 @@ services: - MongoDatabase=MarketingDb - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq} - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110. + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 - CampaignDetailFunctionUri=${ESHOP_AZUREFUNC_CAMPAIGN_DETAILS_URI} - PicBaseUrl=${ESHOP_AZURE_STORAGE_MARKETING:-http://localhost:5110/api/v1/campaigns/[0]/pic/} - AzureStorageAccountName=${ESHOP_AZURE_STORAGE_MARKETING_NAME} @@ -137,7 +144,7 @@ services: payment.api: environment: - ASPNETCORE_ENVIRONMENT=Development - - ASPNETCORE_URLS=http://0.0.0.0:5108 + - ASPNETCORE_URLS=http://0.0.0.0:80 - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq} ports: - "5108:80" @@ -149,6 +156,7 @@ services: - ConnectionString=${ESHOP_AZURE_COSMOSDB:-mongodb://nosql.data} - Database=LocationsDb - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5110. + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq} ports: - "5109:80" diff --git a/docker-compose.yml b/docker-compose.yml index 699415dff..272843e9f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: depends_on: - basket.data - identity.api + - rabbitmq catalog.api: image: eshop/catalog.api:${TAG:-latest} diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index 26dcd6529..a6b95198a 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -19,8 +19,8 @@ - + diff --git a/src/Services/Basket/Basket.API/Controllers/BasketController.cs b/src/Services/Basket/Basket.API/Controllers/BasketController.cs index 1d00f5f00..9219cfad3 100644 --- a/src/Services/Basket/Basket.API/Controllers/BasketController.cs +++ b/src/Services/Basket/Basket.API/Controllers/BasketController.cs @@ -13,7 +13,7 @@ using Basket.API.Model; namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers { - [Route("/")] + [Route("api/v1/[controller]")] [Authorize] public class BasketController : Controller { diff --git a/src/Services/Basket/Basket.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs b/src/Services/Basket/Basket.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs new file mode 100644 index 000000000..f27a3c209 --- /dev/null +++ b/src/Services/Basket/Basket.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Authorization; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Basket.API.Infrastructure.Filters +{ + public class AuthorizeCheckOperationFilter : IOperationFilter + { + public void Apply(Operation operation, OperationFilterContext context) + { + // Check for authorize attribute + var hasAuthorize = context.ApiDescription.ControllerAttributes().OfType().Any() || + context.ApiDescription.ActionAttributes().OfType().Any(); + + if (hasAuthorize) + { + operation.Responses.Add("401", new Response { Description = "Unauthorized" }); + operation.Responses.Add("403", new Response { Description = "Forbidden" }); + + operation.Security = new List>>(); + operation.Security.Add(new Dictionary> + { + { "oauth2", new [] { "basketapi" } } + }); + } + } + } +} \ No newline at end of file diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index 771ed212d..13246e873 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -28,6 +28,8 @@ using System.Threading.Tasks; using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; using Microsoft.Azure.ServiceBus; +using Swashbuckle.AspNetCore.Swagger; +using System.Collections.Generic; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -47,19 +49,18 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) - { - - services.AddHealthChecks(checks => - { - checks.AddValueTaskCheck("HTTP Endpoint", () => new ValueTask(HealthCheckResult.Healthy("Ok"))); - }); - + { // Add framework services. services.AddMvc(options => { options.Filters.Add(typeof(HttpGlobalExceptionFilter)); }).AddControllersAsServices(); + services.AddHealthChecks(checks => + { + checks.AddValueTaskCheck("HTTP Endpoint", () => new ValueTask(HealthCheckResult.Healthy("Ok"))); + }); + services.Configure(Configuration); //By connecting here we are making sure that our service @@ -118,6 +119,20 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API Description = "The Basket Service HTTP API", TermsOfService = "Terms Of Service" }); + + options.AddSecurityDefinition("oauth2", new OAuth2Scheme + { + Type = "oauth2", + Flow = "implicit", + AuthorizationUrl = $"{Configuration.GetValue("IdentityUrlExternal")}/connect/authorize", + TokenUrl = $"{Configuration.GetValue("IdentityUrlExternal")}/connect/token", + Scopes = new Dictionary() + { + { "basket", "Basket API" } + } + }); + + options.OperationFilter(); }); services.AddCors(options => @@ -185,6 +200,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API .UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + c.ConfigureOAuth2("basketswaggerui", "", "", "Basket Swagger UI"); }); ConfigureEventBus(app); diff --git a/src/Services/Identity/Identity.API/Configuration/Config.cs b/src/Services/Identity/Identity.API/Configuration/Config.cs index bce62b039..412220673 100644 --- a/src/Services/Identity/Identity.API/Configuration/Config.cs +++ b/src/Services/Identity/Identity.API/Configuration/Config.cs @@ -146,6 +146,66 @@ namespace Identity.API.Configuration "locations", "marketing" }, + }, + new Client + { + ClientId = "locationsswaggerui", + ClientName = "Locations Swagger UI", + AllowedGrantTypes = GrantTypes.Implicit, + AllowAccessTokensViaBrowser = true, + + RedirectUris = { $"{clientsUrl["LocationsApi"]}/swagger/o2c.html" }, + PostLogoutRedirectUris = { $"{clientsUrl["LocationsApi"]}/swagger/" }, + + AllowedScopes = + { + "locations" + } + }, + new Client + { + ClientId = "marketingswaggerui", + ClientName = "Marketing Swagger UI", + AllowedGrantTypes = GrantTypes.Implicit, + AllowAccessTokensViaBrowser = true, + + RedirectUris = { $"{clientsUrl["MarketingApi"]}/swagger/o2c.html" }, + PostLogoutRedirectUris = { $"{clientsUrl["MarketingApi"]}/swagger/" }, + + AllowedScopes = + { + "marketing" + } + }, + new Client + { + ClientId = "basketswaggerui", + ClientName = "Basket Swagger UI", + AllowedGrantTypes = GrantTypes.Implicit, + AllowAccessTokensViaBrowser = true, + + RedirectUris = { $"{clientsUrl["BasketApi"]}/swagger/o2c.html" }, + PostLogoutRedirectUris = { $"{clientsUrl["BasketApi"]}/swagger/" }, + + AllowedScopes = + { + "basket" + } + }, + new Client + { + ClientId = "orderingswaggerui", + ClientName = "Ordering Swagger UI", + AllowedGrantTypes = GrantTypes.Implicit, + AllowAccessTokensViaBrowser = true, + + RedirectUris = { $"{clientsUrl["OrderingApi"]}/swagger/o2c.html" }, + PostLogoutRedirectUris = { $"{clientsUrl["OrderingApi"]}/swagger/" }, + + AllowedScopes = + { + "orders" + } } }; } diff --git a/src/Services/Identity/Identity.API/Startup.cs b/src/Services/Identity/Identity.API/Startup.cs index bc8ca3cac..0ba915cf3 100644 --- a/src/Services/Identity/Identity.API/Startup.cs +++ b/src/Services/Identity/Identity.API/Startup.cs @@ -163,6 +163,10 @@ namespace eShopOnContainers.Identity clientUrls.Add("Mvc", Configuration.GetValue("MvcClient")); clientUrls.Add("Spa", Configuration.GetValue("SpaClient")); clientUrls.Add("Xamarin", Configuration.GetValue("XamarinCallback")); + clientUrls.Add("LocationsApi", Configuration.GetValue("LocationApiClient")); + clientUrls.Add("MarketingApi", Configuration.GetValue("MarketingApiClient")); + clientUrls.Add("BasketApi", Configuration.GetValue("BasketApiClient")); + clientUrls.Add("OrderingApi", Configuration.GetValue("OrderingApiClient")); using (var serviceScope = app.ApplicationServices.GetService().CreateScope()) { diff --git a/src/Services/Location/Locations.API/Dockerfile b/src/Services/Location/Locations.API/Dockerfile index 3d00f01a9..2e5a500b6 100644 --- a/src/Services/Location/Locations.API/Dockerfile +++ b/src/Services/Location/Locations.API/Dockerfile @@ -1,4 +1,4 @@ -FROM microsoft/aspnetcore:1.1 +FROM microsoft/aspnetcore:1.1.2 ARG source WORKDIR /app EXPOSE 80 diff --git a/src/Services/Location/Locations.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs b/src/Services/Location/Locations.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs index 5a9d9e12a..8eccc83cf 100644 --- a/src/Services/Location/Locations.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs +++ b/src/Services/Location/Locations.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs @@ -23,7 +23,7 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Filt operation.Security = new List>>(); operation.Security.Add(new Dictionary> { - { "oauth2", new [] { "api1" } } + { "oauth2", new [] { "locationsapi" } } }); } } diff --git a/src/Services/Location/Locations.API/Startup.cs b/src/Services/Location/Locations.API/Startup.cs index da95f8b09..34fd8cb75 100644 --- a/src/Services/Location/Locations.API/Startup.cs +++ b/src/Services/Location/Locations.API/Startup.cs @@ -107,8 +107,8 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API { Type = "oauth2", Flow = "implicit", - AuthorizationUrl = "http://localhost:5105/connect/authorize", - TokenUrl = "http://localhost:5105/connect/token", + AuthorizationUrl = $"{Configuration.GetValue("IdentityUrlExternal")}/connect/authorize", + TokenUrl = $"{Configuration.GetValue("IdentityUrlExternal")}/connect/token", Scopes = new Dictionary() { { "locations", "Locations API" } @@ -158,7 +158,7 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API .UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); - c.ConfigureOAuth2("swaggerui", "", "", "Swagger UI"); + c.ConfigureOAuth2("locationsswaggerui", "", "", "Locations Swagger UI"); }); LocationsContextSeed.SeedAsync(app, loggerFactory) diff --git a/src/Services/Marketing/Marketing.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs b/src/Services/Marketing/Marketing.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs new file mode 100644 index 000000000..d1540ba3a --- /dev/null +++ b/src/Services/Marketing/Marketing.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Authorization; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Filters +{ + public class AuthorizeCheckOperationFilter : IOperationFilter + { + public void Apply(Operation operation, OperationFilterContext context) + { + // Check for authorize attribute + var hasAuthorize = context.ApiDescription.ControllerAttributes().OfType().Any() || + context.ApiDescription.ActionAttributes().OfType().Any(); + + if (hasAuthorize) + { + operation.Responses.Add("401", new Response { Description = "Unauthorized" }); + operation.Responses.Add("403", new Response { Description = "Forbidden" }); + + operation.Security = new List>>(); + operation.Security.Add(new Dictionary> + { + { "oauth2", new [] { "marketingapi" } } + }); + } + } + } +} \ No newline at end of file diff --git a/src/Services/Marketing/Marketing.API/Startup.cs b/src/Services/Marketing/Marketing.API/Startup.cs index 75009901b..4c39d9524 100644 --- a/src/Services/Marketing/Marketing.API/Startup.cs +++ b/src/Services/Marketing/Marketing.API/Startup.cs @@ -28,7 +28,8 @@ using System.Threading.Tasks; using Extensions.HealthChecks; using Marketing.API.IntegrationEvents.Handlers; - + using Swashbuckle.AspNetCore.Swagger; + using System.Collections.Generic; public class Startup { @@ -129,6 +130,20 @@ Description = "The Marketing Service HTTP API", TermsOfService = "Terms Of Service" }); + + options.AddSecurityDefinition("oauth2", new OAuth2Scheme + { + Type = "oauth2", + Flow = "implicit", + AuthorizationUrl = $"{Configuration.GetValue("IdentityUrlExternal")}/connect/authorize", + TokenUrl = $"{Configuration.GetValue("IdentityUrlExternal")}/connect/token", + Scopes = new Dictionary() + { + { "marketing", "Marketing API" } + } + }); + + options.OperationFilter(); }); services.AddCors(options => @@ -171,6 +186,7 @@ .UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + c.ConfigureOAuth2("marketingswaggerui", "", "", "Marketing Swagger UI"); }); var context = (MarketingContext)app diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs b/src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs new file mode 100644 index 000000000..3b569e0ba --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Authorization; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ordering.API.Infrastructure.Filters +{ + public class AuthorizeCheckOperationFilter : IOperationFilter + { + public void Apply(Operation operation, OperationFilterContext context) + { + // Check for authorize attribute + var hasAuthorize = context.ApiDescription.ControllerAttributes().OfType().Any() || + context.ApiDescription.ActionAttributes().OfType().Any(); + + if (hasAuthorize) + { + operation.Responses.Add("401", new Response { Description = "Unauthorized" }); + operation.Responses.Add("403", new Response { Description = "Forbidden" }); + + operation.Security = new List>>(); + operation.Security.Add(new Dictionary> + { + { "oauth2", new [] { "orderingapi" } } + }); + } + } + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 15a966c1a..8a35f534c 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -5,6 +5,7 @@ using Autofac.Extensions.DependencyInjection; using global::Ordering.API.Application.IntegrationEvents; using global::Ordering.API.Application.IntegrationEvents.Events; + using global::Ordering.API.Infrastructure.Filters; using Infrastructure; using Infrastructure.AutofacModules; using Infrastructure.Filters; @@ -26,7 +27,9 @@ using Ordering.Infrastructure; using Polly; using RabbitMQ.Client; + using Swashbuckle.AspNetCore.Swagger; using System; + using System.Collections.Generic; using System.Data.Common; using System.Data.SqlClient; using System.Reflection; @@ -98,6 +101,20 @@ Description = "The Ordering Service HTTP API", TermsOfService = "Terms Of Service" }); + + options.AddSecurityDefinition("oauth2", new OAuth2Scheme + { + Type = "oauth2", + Flow = "implicit", + AuthorizationUrl = $"{Configuration.GetValue("IdentityUrlExternal")}/connect/authorize", + TokenUrl = $"{Configuration.GetValue("IdentityUrlExternal")}/connect/token", + Scopes = new Dictionary() + { + { "orders", "Ordering API" } + } + }); + + options.OperationFilter(); }); services.AddCors(options => @@ -174,6 +191,7 @@ .UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); + c.ConfigureOAuth2("orderingswaggerui", "", "", "Ordering Swagger UI"); }); WaitForSqlAvailabilityAsync(loggerFactory, app, env).Wait(); diff --git a/src/Services/Payment/Payment.API/Controllers/HomeController.cs b/src/Services/Payment/Payment.API/Controllers/HomeController.cs index 1372ea720..ae1b931ed 100644 --- a/src/Services/Payment/Payment.API/Controllers/HomeController.cs +++ b/src/Services/Payment/Payment.API/Controllers/HomeController.cs @@ -13,7 +13,7 @@ namespace Payment.API.Controllers // GET: // public IActionResult Index() { - return new RedirectResult("~/swagger/ui"); + return new RedirectResult("~/swagger"); } } } diff --git a/src/Services/Payment/Payment.API/Dockerfile b/src/Services/Payment/Payment.API/Dockerfile index 0bb3473d9..522c20056 100644 --- a/src/Services/Payment/Payment.API/Dockerfile +++ b/src/Services/Payment/Payment.API/Dockerfile @@ -1,4 +1,4 @@ -FROM microsoft/aspnetcore:1.1 +FROM microsoft/aspnetcore:1.1.2 ARG source WORKDIR /app EXPOSE 80